/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 breezy/bzr/remote.py

  • Committer: Jelmer Vernooij
  • Date: 2018-08-14 01:15:02 UTC
  • mto: This revision was merged to the branch mainline in revision 7078.
  • Revision ID: jelmer@jelmer.uk-20180814011502-5zaydaq02vc2qxo1
Fix tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2012 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
 
17
19
import bz2
 
20
import os
 
21
import sys
 
22
import zlib
18
23
 
19
 
from bzrlib import (
 
24
from .. import (
20
25
    bencode,
21
26
    branch,
22
 
    bzrdir,
23
 
    config,
 
27
    bzr as _mod_bzr,
 
28
    config as _mod_config,
 
29
    controldir,
24
30
    debug,
25
31
    errors,
 
32
    gpg,
26
33
    graph,
27
34
    lock,
28
35
    lockdir,
29
 
    repository,
 
36
    osutils,
 
37
    registry,
30
38
    repository as _mod_repository,
31
 
    revision,
32
39
    revision as _mod_revision,
33
40
    static_tuple,
34
 
    symbol_versioning,
35
 
)
36
 
from bzrlib.branch import BranchReferenceFormat
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 (
 
41
    testament as _mod_testament,
 
42
    urlutils,
 
43
    )
 
44
from . import (
 
45
    branch as bzrbranch,
 
46
    bzrdir as _mod_bzrdir,
 
47
    inventory_delta,
 
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 (
40
55
    NoSuchRevision,
41
56
    SmartProtocolError,
42
57
    )
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.trace import mutter, note, warning
 
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
47
79
 
48
80
 
49
81
class _RpcHelper(object):
52
84
    def _call(self, method, *args, **err_context):
53
85
        try:
54
86
            return self._client.call(method, *args)
55
 
        except errors.ErrorFromSmartServer, err:
 
87
        except errors.ErrorFromSmartServer as err:
56
88
            self._translate_error(err, **err_context)
57
89
 
58
90
    def _call_expecting_body(self, method, *args, **err_context):
59
91
        try:
60
92
            return self._client.call_expecting_body(method, *args)
61
 
        except errors.ErrorFromSmartServer, err:
 
93
        except errors.ErrorFromSmartServer as err:
62
94
            self._translate_error(err, **err_context)
63
95
 
64
96
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
65
97
        try:
66
98
            return self._client.call_with_body_bytes(method, args, body_bytes)
67
 
        except errors.ErrorFromSmartServer, err:
 
99
        except errors.ErrorFromSmartServer as err:
68
100
            self._translate_error(err, **err_context)
69
101
 
70
102
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
72
104
        try:
73
105
            return self._client.call_with_body_bytes_expecting_body(
74
106
                method, args, body_bytes)
75
 
        except errors.ErrorFromSmartServer, err:
 
107
        except errors.ErrorFromSmartServer as err:
76
108
            self._translate_error(err, **err_context)
77
109
 
78
110
 
79
111
def response_tuple_to_repo_format(response):
80
112
    """Convert a response tuple describing a repository format to a format."""
81
113
    format = RemoteRepositoryFormat()
82
 
    format._rich_root_data = (response[0] == 'yes')
83
 
    format._supports_tree_reference = (response[1] == 'yes')
84
 
    format._supports_external_lookups = (response[2] == 'yes')
 
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')
85
117
    format._network_name = response[3]
86
118
    return format
87
119
 
88
120
 
89
 
# Note: RemoteBzrDirFormat is in bzrdir.py
90
 
 
91
 
class RemoteBzrDir(BzrDir, _RpcHelper):
 
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('unexpected response code %s' % (response,))
 
179
        format = RemoteBzrDirFormat()
 
180
        self._supply_sub_formats_to(format)
 
181
        return RemoteBzrDir(transport, format)
 
182
 
 
183
    def parse_NoneTrueFalse(self, arg):
 
184
        if not arg:
 
185
            return None
 
186
        if arg == b'False':
 
187
            return False
 
188
        if arg == b'True':
 
189
            return True
 
190
        raise AssertionError("invalid arg %r" % arg)
 
191
 
 
192
    def _serialize_NoneTrueFalse(self, arg):
 
193
        if arg is False:
 
194
            return b'False'
 
195
        if arg:
 
196
            return b'True'
 
197
        return b''
 
198
 
 
199
    def _serialize_NoneString(self, arg):
 
200
        return arg or b''
 
201
 
 
202
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
 
203
        create_prefix=False, force_new_repo=False, stacked_on=None,
 
204
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
 
205
        shared_repo=False):
 
206
        try:
 
207
            # hand off the request to the smart server
 
208
            client_medium = transport.get_smart_medium()
 
209
        except errors.NoSmartMedium:
 
210
            do_vfs = True
 
211
        else:
 
212
            # Decline to open it if the server doesn't support our required
 
213
            # version (3) so that the VFS-based transport will do it.
 
214
            if client_medium.should_probe():
 
215
                try:
 
216
                    server_version = client_medium.protocol_version()
 
217
                    if server_version != '2':
 
218
                        do_vfs = True
 
219
                    else:
 
220
                        do_vfs = False
 
221
                except errors.SmartProtocolError:
 
222
                    # Apparently there's no usable smart server there, even though
 
223
                    # the medium supports the smart protocol.
 
224
                    do_vfs = True
 
225
            else:
 
226
                do_vfs = False
 
227
        if not do_vfs:
 
228
            client = _SmartClient(client_medium)
 
229
            path = client.remote_path_from_transport(transport)
 
230
            if client_medium._is_remote_before((1, 16)):
 
231
                do_vfs = True
 
232
        if do_vfs:
 
233
            # TODO: lookup the local format from a server hint.
 
234
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
235
            self._supply_sub_formats_to(local_dir_format)
 
236
            return local_dir_format.initialize_on_transport_ex(transport,
 
237
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
238
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
239
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
240
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
241
                vfs_only=True)
 
242
        return self._initialize_on_transport_ex_rpc(client, path, transport,
 
243
            use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
244
            stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
 
245
 
 
246
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
 
247
        use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
248
        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
 
249
        args = []
 
250
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
 
251
        args.append(self._serialize_NoneTrueFalse(create_prefix))
 
252
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
 
253
        args.append(self._serialize_NoneString(stacked_on))
 
254
        # stack_on_pwd is often/usually our transport
 
255
        if stack_on_pwd:
 
256
            try:
 
257
                stack_on_pwd = transport.relpath(stack_on_pwd).encode('utf-8')
 
258
                if not stack_on_pwd:
 
259
                    stack_on_pwd = b'.'
 
260
            except errors.PathNotChild:
 
261
                pass
 
262
        args.append(self._serialize_NoneString(stack_on_pwd))
 
263
        args.append(self._serialize_NoneString(repo_format_name))
 
264
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
 
265
        args.append(self._serialize_NoneTrueFalse(shared_repo))
 
266
        request_network_name = self._network_name or \
 
267
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
 
268
        try:
 
269
            response = client.call(b'BzrDirFormat.initialize_ex_1.16',
 
270
                request_network_name, path, *args)
 
271
        except errors.UnknownSmartMethod:
 
272
            client._medium._remember_remote_is_before((1, 16))
 
273
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
274
            self._supply_sub_formats_to(local_dir_format)
 
275
            return local_dir_format.initialize_on_transport_ex(transport,
 
276
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
277
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
278
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
279
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
280
                vfs_only=True)
 
281
        except errors.ErrorFromSmartServer as err:
 
282
            _translate_error(err, path=path)
 
283
        repo_path = response[0]
 
284
        bzrdir_name = response[6]
 
285
        require_stacking = response[7]
 
286
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
 
287
        format = RemoteBzrDirFormat()
 
288
        format._network_name = bzrdir_name
 
289
        self._supply_sub_formats_to(format)
 
290
        bzrdir = RemoteBzrDir(transport, format, _client=client)
 
291
        if repo_path:
 
292
            repo_format = response_tuple_to_repo_format(response[1:])
 
293
            if repo_path == b'.':
 
294
                repo_path = b''
 
295
            repo_path = repo_path.decode('utf-8')
 
296
            if repo_path:
 
297
                repo_bzrdir_format = RemoteBzrDirFormat()
 
298
                repo_bzrdir_format._network_name = response[5]
 
299
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
 
300
                    repo_bzrdir_format)
 
301
            else:
 
302
                repo_bzr = bzrdir
 
303
            final_stack = response[8] or None
 
304
            if final_stack:
 
305
                final_stack = final_stack.decode('utf-8')
 
306
            final_stack_pwd = response[9] or None
 
307
            if final_stack_pwd:
 
308
                final_stack_pwd = urlutils.join(
 
309
                    transport.base, final_stack_pwd.decode('utf-8'))
 
310
            remote_repo = RemoteRepository(repo_bzr, repo_format)
 
311
            if len(response) > 10:
 
312
                # Updated server verb that locks remotely.
 
313
                repo_lock_token = response[10] or None
 
314
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
 
315
                if repo_lock_token:
 
316
                    remote_repo.dont_leave_lock_in_place()
 
317
            else:
 
318
                remote_repo.lock_write()
 
319
            policy = _mod_bzrdir.UseExistingRepository(remote_repo,
 
320
                    final_stack, final_stack_pwd, require_stacking)
 
321
            policy.acquire_repository()
 
322
        else:
 
323
            remote_repo = None
 
324
            policy = None
 
325
        bzrdir._format.set_branch_format(self.get_branch_format())
 
326
        if require_stacking:
 
327
            # The repo has already been created, but we need to make sure that
 
328
            # we'll make a stackable branch.
 
329
            bzrdir._format.require_stacking(_skip_repo=True)
 
330
        return remote_repo, bzrdir, require_stacking, policy
 
331
 
 
332
    def _open(self, transport):
 
333
        return RemoteBzrDir(transport, self)
 
334
 
 
335
    def __eq__(self, other):
 
336
        if not isinstance(other, RemoteBzrDirFormat):
 
337
            return False
 
338
        return self.get_format_description() == other.get_format_description()
 
339
 
 
340
    def __return_repository_format(self):
 
341
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
 
342
        # repository format has been asked for, tell the RemoteRepositoryFormat
 
343
        # that it should use that for init() etc.
 
344
        result = RemoteRepositoryFormat()
 
345
        custom_format = getattr(self, '_repository_format', None)
 
346
        if custom_format:
 
347
            if isinstance(custom_format, RemoteRepositoryFormat):
 
348
                return custom_format
 
349
            else:
 
350
                # We will use the custom format to create repositories over the
 
351
                # wire; expose its details like rich_root_data for code to
 
352
                # query
 
353
                result._custom_format = custom_format
 
354
        return result
 
355
 
 
356
    def get_branch_format(self):
 
357
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
 
358
        if not isinstance(result, RemoteBranchFormat):
 
359
            new_result = RemoteBranchFormat()
 
360
            new_result._custom_format = result
 
361
            # cache the result
 
362
            self.set_branch_format(new_result)
 
363
            result = new_result
 
364
        return result
 
365
 
 
366
    repository_format = property(__return_repository_format,
 
367
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
 
368
 
 
369
 
 
370
class RemoteControlStore(_mod_config.IniFileStore):
 
371
    """Control store which attempts to use HPSS calls to retrieve control store.
 
372
 
 
373
    Note that this is specific to bzr-based formats.
 
374
    """
 
375
 
 
376
    def __init__(self, bzrdir):
 
377
        super(RemoteControlStore, self).__init__()
 
378
        self.controldir = bzrdir
 
379
        self._real_store = None
 
380
 
 
381
    def lock_write(self, token=None):
 
382
        self._ensure_real()
 
383
        return self._real_store.lock_write(token)
 
384
 
 
385
    def unlock(self):
 
386
        self._ensure_real()
 
387
        return self._real_store.unlock()
 
388
 
 
389
    def save(self):
 
390
        with self.lock_write():
 
391
            # We need to be able to override the undecorated implementation
 
392
            self.save_without_locking()
 
393
 
 
394
    def save_without_locking(self):
 
395
        super(RemoteControlStore, self).save()
 
396
 
 
397
    def _ensure_real(self):
 
398
        self.controldir._ensure_real()
 
399
        if self._real_store is None:
 
400
            self._real_store = _mod_config.ControlStore(self.controldir)
 
401
 
 
402
    def external_url(self):
 
403
        return urlutils.join(self.branch.user_url, 'control.conf')
 
404
 
 
405
    def _load_content(self):
 
406
        medium = self.controldir._client._medium
 
407
        path = self.controldir._path_for_remote_call(self.controldir._client)
 
408
        try:
 
409
            response, handler = self.controldir._call_expecting_body(
 
410
                b'BzrDir.get_config_file', path)
 
411
        except errors.UnknownSmartMethod:
 
412
            self._ensure_real()
 
413
            return self._real_store._load_content()
 
414
        if len(response) and response[0] != b'ok':
 
415
            raise errors.UnexpectedSmartServerResponse(response)
 
416
        return handler.read_body_bytes()
 
417
 
 
418
    def _save_content(self, content):
 
419
        # FIXME JRV 2011-11-22: Ideally this should use a
 
420
        # HPSS call too, but at the moment it is not possible
 
421
        # to write lock control directories.
 
422
        self._ensure_real()
 
423
        return self._real_store._save_content(content)
 
424
 
 
425
 
 
426
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
92
427
    """Control directory on a remote server, accessed via bzr:// or similar."""
93
428
 
94
429
    def __init__(self, transport, format, _client=None, _force_probe=False):
97
432
        :param _client: Private parameter for testing. Disables probing and the
98
433
            use of a real bzrdir.
99
434
        """
100
 
        BzrDir.__init__(self, transport, format)
 
435
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
101
436
        # this object holds a delegated bzrdir that uses file-level operations
102
437
        # to talk to the other side
103
438
        self._real_bzrdir = None
133
468
            self._rpc_open(path)
134
469
 
135
470
    def _rpc_open_2_1(self, path):
136
 
        response = self._call('BzrDir.open_2.1', path)
137
 
        if response == ('no',):
 
471
        response = self._call(b'BzrDir.open_2.1', path)
 
472
        if response == (b'no',):
138
473
            raise errors.NotBranchError(path=self.root_transport.base)
139
 
        elif response[0] == 'yes':
140
 
            if response[1] == 'yes':
 
474
        elif response[0] == b'yes':
 
475
            if response[1] == b'yes':
141
476
                self._has_working_tree = True
142
 
            elif response[1] == 'no':
 
477
            elif response[1] == b'no':
143
478
                self._has_working_tree = False
144
479
            else:
145
480
                raise errors.UnexpectedSmartServerResponse(response)
147
482
            raise errors.UnexpectedSmartServerResponse(response)
148
483
 
149
484
    def _rpc_open(self, path):
150
 
        response = self._call('BzrDir.open', path)
151
 
        if response not in [('yes',), ('no',)]:
 
485
        response = self._call(b'BzrDir.open', path)
 
486
        if response not in [(b'yes',), (b'no',)]:
152
487
            raise errors.UnexpectedSmartServerResponse(response)
153
 
        if response == ('no',):
 
488
        if response == (b'no',):
154
489
            raise errors.NotBranchError(path=self.root_transport.base)
155
490
 
156
491
    def _ensure_real(self):
163
498
                import traceback
164
499
                warning('VFS BzrDir access triggered\n%s',
165
500
                    ''.join(traceback.format_stack()))
166
 
            self._real_bzrdir = BzrDir.open_from_transport(
167
 
                self.root_transport, _server_formats=False)
 
501
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
 
502
                self.root_transport, probers=[_mod_bzr.BzrProber])
168
503
            self._format._network_name = \
169
504
                self._real_bzrdir._format.network_name()
170
505
 
175
510
        # Prevent aliasing problems in the next_open_branch_result cache.
176
511
        # See create_branch for rationale.
177
512
        self._next_open_branch_result = None
178
 
        return BzrDir.break_lock(self)
 
513
        return _mod_bzrdir.BzrDir.break_lock(self)
 
514
 
 
515
    def _vfs_checkout_metadir(self):
 
516
        self._ensure_real()
 
517
        return self._real_bzrdir.checkout_metadir()
 
518
 
 
519
    def checkout_metadir(self):
 
520
        """Retrieve the controldir format to use for checkouts of this one.
 
521
        """
 
522
        medium = self._client._medium
 
523
        if medium._is_remote_before((2, 5)):
 
524
            return self._vfs_checkout_metadir()
 
525
        path = self._path_for_remote_call(self._client)
 
526
        try:
 
527
            response = self._client.call(b'BzrDir.checkout_metadir',
 
528
                path)
 
529
        except errors.UnknownSmartMethod:
 
530
            medium._remember_remote_is_before((2, 5))
 
531
            return self._vfs_checkout_metadir()
 
532
        if len(response) != 3:
 
533
            raise errors.UnexpectedSmartServerResponse(response)
 
534
        control_name, repo_name, branch_name = response
 
535
        try:
 
536
            format = controldir.network_format_registry.get(control_name)
 
537
        except KeyError:
 
538
            raise errors.UnknownFormatError(kind='control',
 
539
                format=control_name)
 
540
        if repo_name:
 
541
            try:
 
542
                repo_format = _mod_repository.network_format_registry.get(
 
543
                    repo_name)
 
544
            except KeyError:
 
545
                raise errors.UnknownFormatError(kind='repository',
 
546
                    format=repo_name)
 
547
            format.repository_format = repo_format
 
548
        if branch_name:
 
549
            try:
 
550
                format.set_branch_format(
 
551
                    branch.network_format_registry.get(branch_name))
 
552
            except KeyError:
 
553
                raise errors.UnknownFormatError(kind='branch',
 
554
                    format=branch_name)
 
555
        return format
179
556
 
180
557
    def _vfs_cloning_metadir(self, require_stacking=False):
181
558
        self._ensure_real()
186
563
        medium = self._client._medium
187
564
        if medium._is_remote_before((1, 13)):
188
565
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
189
 
        verb = 'BzrDir.cloning_metadir'
 
566
        verb = b'BzrDir.cloning_metadir'
190
567
        if require_stacking:
191
 
            stacking = 'True'
 
568
            stacking = b'True'
192
569
        else:
193
 
            stacking = 'False'
 
570
            stacking = b'False'
194
571
        path = self._path_for_remote_call(self._client)
195
572
        try:
196
573
            response = self._call(verb, path, stacking)
197
574
        except errors.UnknownSmartMethod:
198
575
            medium._remember_remote_is_before((1, 13))
199
576
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
 
        except errors.UnknownErrorFromSmartServer, err:
201
 
            if err.error_tuple != ('BranchReference',):
 
577
        except errors.UnknownErrorFromSmartServer as err:
 
578
            if err.error_tuple != (b'BranchReference',):
202
579
                raise
203
580
            # We need to resolve the branch reference to determine the
204
581
            # cloning_metadir.  This causes unnecessary RPCs to open the
205
582
            # referenced branch (and bzrdir, etc) but only when the caller
206
583
            # didn't already resolve the branch reference.
207
584
            referenced_branch = self.open_branch()
208
 
            return referenced_branch.bzrdir.cloning_metadir()
 
585
            return referenced_branch.controldir.cloning_metadir()
209
586
        if len(response) != 3:
210
587
            raise errors.UnexpectedSmartServerResponse(response)
211
588
        control_name, repo_name, branch_info = response
212
589
        if len(branch_info) != 2:
213
590
            raise errors.UnexpectedSmartServerResponse(response)
214
591
        branch_ref, branch_name = branch_info
215
 
        format = bzrdir.network_format_registry.get(control_name)
 
592
        try:
 
593
            format = controldir.network_format_registry.get(control_name)
 
594
        except KeyError:
 
595
            raise errors.UnknownFormatError(kind='control', format=control_name)
 
596
 
216
597
        if repo_name:
217
 
            format.repository_format = repository.network_format_registry.get(
218
 
                repo_name)
219
 
        if branch_ref == 'ref':
 
598
            try:
 
599
                format.repository_format = _mod_repository.network_format_registry.get(
 
600
                    repo_name)
 
601
            except KeyError:
 
602
                raise errors.UnknownFormatError(kind='repository',
 
603
                    format=repo_name)
 
604
        if branch_ref == b'ref':
220
605
            # XXX: we need possible_transports here to avoid reopening the
221
606
            # connection to the referenced location
222
 
            ref_bzrdir = BzrDir.open(branch_name)
 
607
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
223
608
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
609
            format.set_branch_format(branch_format)
225
 
        elif branch_ref == 'branch':
 
610
        elif branch_ref == b'branch':
226
611
            if branch_name:
227
 
                format.set_branch_format(
228
 
                    branch.network_format_registry.get(branch_name))
 
612
                try:
 
613
                    branch_format = branch.network_format_registry.get(
 
614
                        branch_name)
 
615
                except KeyError:
 
616
                    raise errors.UnknownFormatError(kind='branch',
 
617
                        format=branch_name)
 
618
                format.set_branch_format(branch_format)
229
619
        else:
230
620
            raise errors.UnexpectedSmartServerResponse(response)
231
621
        return format
241
631
 
242
632
    def destroy_repository(self):
243
633
        """See BzrDir.destroy_repository"""
244
 
        self._ensure_real()
245
 
        self._real_bzrdir.destroy_repository()
 
634
        path = self._path_for_remote_call(self._client)
 
635
        try:
 
636
            response = self._call(b'BzrDir.destroy_repository', path)
 
637
        except errors.UnknownSmartMethod:
 
638
            self._ensure_real()
 
639
            self._real_bzrdir.destroy_repository()
 
640
            return
 
641
        if response[0] != b'ok':
 
642
            raise SmartProtocolError('unexpected response code %s' % (response,))
246
643
 
247
 
    def create_branch(self, name=None):
 
644
    def create_branch(self, name=None, repository=None,
 
645
                      append_revisions_only=None):
 
646
        if name is None:
 
647
            name = self._get_selected_branch()
 
648
        if name != "":
 
649
            raise errors.NoColocatedBranchSupport(self)
248
650
        # as per meta1 formats - just delegate to the format object which may
249
651
        # be parameterised.
250
652
        real_branch = self._format.get_branch_format().initialize(self,
251
 
            name=name)
 
653
            name=name, repository=repository,
 
654
            append_revisions_only=append_revisions_only)
252
655
        if not isinstance(real_branch, RemoteBranch):
253
 
            result = RemoteBranch(self, self.find_repository(), real_branch,
254
 
                                  name=name)
 
656
            if not isinstance(repository, RemoteRepository):
 
657
                raise AssertionError(
 
658
                    'need a RemoteRepository to use with RemoteBranch, got %r'
 
659
                    % (repository,))
 
660
            result = RemoteBranch(self, repository, real_branch, name=name)
255
661
        else:
256
662
            result = real_branch
257
663
        # BzrDir.clone_on_transport() uses the result of create_branch but does
265
671
 
266
672
    def destroy_branch(self, name=None):
267
673
        """See BzrDir.destroy_branch"""
268
 
        self._ensure_real()
269
 
        self._real_bzrdir.destroy_branch(name=name)
 
674
        if name is None:
 
675
            name = self._get_selected_branch()
 
676
        if name != "":
 
677
            raise errors.NoColocatedBranchSupport(self)
 
678
        path = self._path_for_remote_call(self._client)
 
679
        try:
 
680
            if name != "":
 
681
                args = (name, )
 
682
            else:
 
683
                args = ()
 
684
            response = self._call(b'BzrDir.destroy_branch', path, *args)
 
685
        except errors.UnknownSmartMethod:
 
686
            self._ensure_real()
 
687
            self._real_bzrdir.destroy_branch(name=name)
 
688
            self._next_open_branch_result = None
 
689
            return
270
690
        self._next_open_branch_result = None
 
691
        if response[0] != b'ok':
 
692
            raise SmartProtocolError('unexpected response code %s' % (response,))
271
693
 
272
 
    def create_workingtree(self, revision_id=None, from_branch=None):
 
694
    def create_workingtree(self, revision_id=None, from_branch=None,
 
695
        accelerator_tree=None, hardlink=False):
273
696
        raise errors.NotLocalUrl(self.transport.base)
274
697
 
275
 
    def find_branch_format(self):
 
698
    def find_branch_format(self, name=None):
276
699
        """Find the branch 'format' for this bzrdir.
277
700
 
278
701
        This might be a synthetic object for e.g. RemoteBranch and SVN.
279
702
        """
280
 
        b = self.open_branch()
 
703
        b = self.open_branch(name=name)
281
704
        return b._format
282
705
 
283
 
    def get_branch_reference(self):
 
706
    def get_branches(self, possible_transports=None, ignore_fallbacks=False):
 
707
        path = self._path_for_remote_call(self._client)
 
708
        try:
 
709
            response, handler = self._call_expecting_body(
 
710
                b'BzrDir.get_branches', path)
 
711
        except errors.UnknownSmartMethod:
 
712
            self._ensure_real()
 
713
            return self._real_bzrdir.get_branches()
 
714
        if response[0] != b"success":
 
715
            raise errors.UnexpectedSmartServerResponse(response)
 
716
        body = bencode.bdecode(handler.read_body_bytes())
 
717
        ret = {}
 
718
        for name, value in viewitems(body):
 
719
            name = name.decode('utf-8')
 
720
            ret[name] = self._open_branch(name, value[0], value[1],
 
721
                possible_transports=possible_transports,
 
722
                ignore_fallbacks=ignore_fallbacks)
 
723
        return ret
 
724
 
 
725
    def set_branch_reference(self, target_branch, name=None):
 
726
        """See BzrDir.set_branch_reference()."""
 
727
        if name is None:
 
728
            name = self._get_selected_branch()
 
729
        if name != "":
 
730
            raise errors.NoColocatedBranchSupport(self)
 
731
        self._ensure_real()
 
732
        return self._real_bzrdir.set_branch_reference(target_branch, name=name)
 
733
 
 
734
    def get_branch_reference(self, name=None):
284
735
        """See BzrDir.get_branch_reference()."""
 
736
        if name is None:
 
737
            name = self._get_selected_branch()
 
738
        if name != "":
 
739
            raise errors.NoColocatedBranchSupport(self)
285
740
        response = self._get_branch_reference()
286
 
        if response[0] == 'ref':
287
 
            return response[1]
 
741
        if response[0] == b'ref':
 
742
            return response[1].decode('utf-8')
288
743
        else:
289
744
            return None
290
745
 
292
747
        path = self._path_for_remote_call(self._client)
293
748
        medium = self._client._medium
294
749
        candidate_calls = [
295
 
            ('BzrDir.open_branchV3', (2, 1)),
296
 
            ('BzrDir.open_branchV2', (1, 13)),
297
 
            ('BzrDir.open_branch', None),
 
750
            (b'BzrDir.open_branchV3', (2, 1)),
 
751
            (b'BzrDir.open_branchV2', (1, 13)),
 
752
            (b'BzrDir.open_branch', None),
298
753
            ]
299
754
        for verb, required_version in candidate_calls:
300
755
            if required_version and medium._is_remote_before(required_version):
307
762
                medium._remember_remote_is_before(required_version)
308
763
            else:
309
764
                break
310
 
        if verb == 'BzrDir.open_branch':
311
 
            if response[0] != 'ok':
 
765
        if verb == b'BzrDir.open_branch':
 
766
            if response[0] != b'ok':
312
767
                raise errors.UnexpectedSmartServerResponse(response)
313
 
            if response[1] != '':
314
 
                return ('ref', response[1])
 
768
            if response[1] != b'':
 
769
                return (b'ref', response[1])
315
770
            else:
316
 
                return ('branch', '')
317
 
        if response[0] not in ('ref', 'branch'):
 
771
                return (b'branch', b'')
 
772
        if response[0] not in (b'ref', b'branch'):
318
773
            raise errors.UnexpectedSmartServerResponse(response)
319
774
        return response
320
775
 
321
 
    def _get_tree_branch(self):
 
776
    def _get_tree_branch(self, name=None):
322
777
        """See BzrDir._get_tree_branch()."""
323
 
        return None, self.open_branch()
 
778
        return None, self.open_branch(name=name)
324
779
 
325
 
    def open_branch(self, name=None, unsupported=False,
326
 
                    ignore_fallbacks=False):
327
 
        if unsupported:
328
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
329
 
        if self._next_open_branch_result is not None:
330
 
            # See create_branch for details.
331
 
            result = self._next_open_branch_result
332
 
            self._next_open_branch_result = None
333
 
            return result
334
 
        response = self._get_branch_reference()
335
 
        if response[0] == 'ref':
 
780
    def _open_branch(self, name, kind, location_or_format,
 
781
                     ignore_fallbacks=False, possible_transports=None):
 
782
        if kind == 'ref':
336
783
            # a branch reference, use the existing BranchReference logic.
337
784
            format = BranchReferenceFormat()
338
785
            return format.open(self, name=name, _found=True,
339
 
                location=response[1], ignore_fallbacks=ignore_fallbacks)
340
 
        branch_format_name = response[1]
 
786
                location=location_or_format, ignore_fallbacks=ignore_fallbacks,
 
787
                possible_transports=possible_transports)
 
788
        branch_format_name = location_or_format
341
789
        if not branch_format_name:
342
790
            branch_format_name = None
343
791
        format = RemoteBranchFormat(network_name=branch_format_name)
344
792
        return RemoteBranch(self, self.find_repository(), format=format,
345
 
            setup_stacking=not ignore_fallbacks, name=name)
 
793
            setup_stacking=not ignore_fallbacks, name=name,
 
794
            possible_transports=possible_transports)
 
795
 
 
796
    def open_branch(self, name=None, unsupported=False,
 
797
                    ignore_fallbacks=False, possible_transports=None):
 
798
        if name is None:
 
799
            name = self._get_selected_branch()
 
800
        if name != "":
 
801
            raise errors.NoColocatedBranchSupport(self)
 
802
        if unsupported:
 
803
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
804
        if self._next_open_branch_result is not None:
 
805
            # See create_branch for details.
 
806
            result = self._next_open_branch_result
 
807
            self._next_open_branch_result = None
 
808
            return result
 
809
        response = self._get_branch_reference()
 
810
        return self._open_branch(name, response[0], response[1],
 
811
            possible_transports=possible_transports,
 
812
            ignore_fallbacks=ignore_fallbacks)
346
813
 
347
814
    def _open_repo_v1(self, path):
348
 
        verb = 'BzrDir.find_repository'
 
815
        verb = b'BzrDir.find_repository'
349
816
        response = self._call(verb, path)
350
 
        if response[0] != 'ok':
 
817
        if response[0] != b'ok':
351
818
            raise errors.UnexpectedSmartServerResponse(response)
352
819
        # servers that only support the v1 method don't support external
353
820
        # references either.
354
821
        self._ensure_real()
355
822
        repo = self._real_bzrdir.open_repository()
356
 
        response = response + ('no', repo._format.network_name())
 
823
        response = response + (b'no', repo._format.network_name())
357
824
        return response, repo
358
825
 
359
826
    def _open_repo_v2(self, path):
360
 
        verb = 'BzrDir.find_repositoryV2'
 
827
        verb = b'BzrDir.find_repositoryV2'
361
828
        response = self._call(verb, path)
362
 
        if response[0] != 'ok':
 
829
        if response[0] != b'ok':
363
830
            raise errors.UnexpectedSmartServerResponse(response)
364
831
        self._ensure_real()
365
832
        repo = self._real_bzrdir.open_repository()
367
834
        return response, repo
368
835
 
369
836
    def _open_repo_v3(self, path):
370
 
        verb = 'BzrDir.find_repositoryV3'
 
837
        verb = b'BzrDir.find_repositoryV3'
371
838
        medium = self._client._medium
372
839
        if medium._is_remote_before((1, 13)):
373
840
            raise errors.UnknownSmartMethod(verb)
376
843
        except errors.UnknownSmartMethod:
377
844
            medium._remember_remote_is_before((1, 13))
378
845
            raise
379
 
        if response[0] != 'ok':
 
846
        if response[0] != b'ok':
380
847
            raise errors.UnexpectedSmartServerResponse(response)
381
848
        return response, None
382
849
 
391
858
            except errors.UnknownSmartMethod:
392
859
                pass
393
860
        if response is None:
394
 
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
395
 
        if response[0] != 'ok':
 
861
            raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
 
862
        if response[0] != b'ok':
396
863
            raise errors.UnexpectedSmartServerResponse(response)
397
864
        if len(response) != 6:
398
865
            raise SmartProtocolError('incorrect response length %s' % (response,))
399
 
        if response[1] == '':
 
866
        if response[1] == b'':
400
867
            # repo is at this dir.
401
868
            format = response_tuple_to_repo_format(response[2:])
402
869
            # Used to support creating a real format instance when needed.
411
878
 
412
879
    def has_workingtree(self):
413
880
        if self._has_working_tree is None:
414
 
            self._ensure_real()
415
 
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
881
            path = self._path_for_remote_call(self._client)
 
882
            try:
 
883
                response = self._call(b'BzrDir.has_workingtree', path)
 
884
            except errors.UnknownSmartMethod:
 
885
                self._ensure_real()
 
886
                self._has_working_tree = self._real_bzrdir.has_workingtree()
 
887
            else:
 
888
                if response[0] not in (b'yes', b'no'):
 
889
                    raise SmartProtocolError('unexpected response code %s' % (response,))
 
890
                self._has_working_tree = (response[0] == b'yes')
416
891
        return self._has_working_tree
417
892
 
418
893
    def open_workingtree(self, recommend_upgrade=True):
423
898
 
424
899
    def _path_for_remote_call(self, client):
425
900
        """Return the path to be used for this bzrdir in a remote call."""
426
 
        return client.remote_path_from_transport(self.root_transport)
 
901
        remote_path = client.remote_path_from_transport(self.root_transport)
 
902
        if sys.version_info[0] == 3:
 
903
            remote_path = remote_path.decode('utf-8')
 
904
        base_url, segment_parameters = urlutils.split_segment_parameters_raw(
 
905
                remote_path)
 
906
        if sys.version_info[0] == 3:
 
907
            base_url = base_url.encode('utf-8')
 
908
        return base_url
427
909
 
428
910
    def get_branch_transport(self, branch_format, name=None):
429
911
        self._ensure_real()
441
923
        """Upgrading of remote bzrdirs is not supported yet."""
442
924
        return False
443
925
 
444
 
    def needs_format_conversion(self, format=None):
 
926
    def needs_format_conversion(self, format):
445
927
        """Upgrading of remote bzrdirs is not supported yet."""
446
 
        if format is None:
447
 
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
448
 
                % 'needs_format_conversion(format=None)')
449
928
        return False
450
929
 
451
 
    def clone(self, url, revision_id=None, force_new_repo=False,
452
 
              preserve_stacking=False):
453
 
        self._ensure_real()
454
 
        return self._real_bzrdir.clone(url, revision_id=revision_id,
455
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
456
 
 
457
930
    def _get_config(self):
458
931
        return RemoteBzrDirConfig(self)
459
932
 
460
 
 
461
 
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
933
    def _get_config_store(self):
 
934
        return RemoteControlStore(self)
 
935
 
 
936
 
 
937
class RemoteInventoryTree(InventoryRevisionTree):
 
938
 
 
939
    def __init__(self, repository, inv, revision_id):
 
940
        super(RemoteInventoryTree, self).__init__(repository, inv, revision_id)
 
941
 
 
942
    def archive(self, format, name, root=None, subdir=None, force_mtime=None):
 
943
        ret = self._repository._revision_archive(
 
944
                self.get_revision_id(), format, name, root, subdir,
 
945
                force_mtime=force_mtime)
 
946
        if ret is None:
 
947
            return super(RemoteInventoryTree, self).archive(
 
948
                format, name, root, subdir, force_mtime=force_mtime)
 
949
        return ret
 
950
 
 
951
    def annotate_iter(self, path, file_id=None,
 
952
                      default_revision=_mod_revision.CURRENT_REVISION):
 
953
        """Return an iterator of revision_id, line tuples.
 
954
 
 
955
        For working trees (and mutable trees in general), the special
 
956
        revision_id 'current:' will be used for lines that are new in this
 
957
        tree, e.g. uncommitted changes.
 
958
        :param file_id: The file to produce an annotated version from
 
959
        :param default_revision: For lines that don't match a basis, mark them
 
960
            with this revision id. Not all implementations will make use of
 
961
            this value.
 
962
        """
 
963
        ret = self._repository._annotate_file_revision(
 
964
                    self.get_revision_id(), path, file_id, default_revision)
 
965
        if ret is None:
 
966
            return super(RemoteInventoryTree, self).annotate_iter(
 
967
                path, file_id, default_revision=default_revision)
 
968
        return ret
 
969
 
 
970
 
 
971
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
462
972
    """Format for repositories accessed over a _SmartClient.
463
973
 
464
974
    Instances of this repository are represented by RemoteRepository
478
988
        to obtain data like the network name.
479
989
    """
480
990
 
481
 
    _matchingbzrdir = RemoteBzrDirFormat()
 
991
    _matchingcontroldir = RemoteBzrDirFormat()
 
992
    supports_full_versioned_files = True
 
993
    supports_leaving_lock = True
 
994
    supports_overriding_transport = False
482
995
 
483
996
    def __init__(self):
484
 
        repository.RepositoryFormat.__init__(self)
 
997
        _mod_repository.RepositoryFormat.__init__(self)
485
998
        self._custom_format = None
486
999
        self._network_name = None
487
1000
        self._creating_bzrdir = None
 
1001
        self._revision_graph_can_have_wrong_parents = None
488
1002
        self._supports_chks = None
489
1003
        self._supports_external_lookups = None
490
1004
        self._supports_tree_reference = None
 
1005
        self._supports_funky_characters = None
 
1006
        self._supports_nesting_repositories = None
491
1007
        self._rich_root_data = None
492
1008
 
493
1009
    def __repr__(self):
522
1038
        return self._supports_external_lookups
523
1039
 
524
1040
    @property
 
1041
    def supports_funky_characters(self):
 
1042
        if self._supports_funky_characters is None:
 
1043
            self._ensure_real()
 
1044
            self._supports_funky_characters = \
 
1045
                self._custom_format.supports_funky_characters
 
1046
        return self._supports_funky_characters
 
1047
 
 
1048
    @property
 
1049
    def supports_nesting_repositories(self):
 
1050
        if self._supports_nesting_repositories is None:
 
1051
            self._ensure_real()
 
1052
            self._supports_nesting_repositories = \
 
1053
                self._custom_format.supports_nesting_repositories
 
1054
        return self._supports_nesting_repositories
 
1055
 
 
1056
    @property
525
1057
    def supports_tree_reference(self):
526
1058
        if self._supports_tree_reference is None:
527
1059
            self._ensure_real()
529
1061
                self._custom_format.supports_tree_reference
530
1062
        return self._supports_tree_reference
531
1063
 
532
 
    def _vfs_initialize(self, a_bzrdir, shared):
 
1064
    @property
 
1065
    def revision_graph_can_have_wrong_parents(self):
 
1066
        if self._revision_graph_can_have_wrong_parents is None:
 
1067
            self._ensure_real()
 
1068
            self._revision_graph_can_have_wrong_parents = \
 
1069
                self._custom_format.revision_graph_can_have_wrong_parents
 
1070
        return self._revision_graph_can_have_wrong_parents
 
1071
 
 
1072
    def _vfs_initialize(self, a_controldir, shared):
533
1073
        """Helper for common code in initialize."""
534
1074
        if self._custom_format:
535
1075
            # Custom format requested
536
 
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
1076
            result = self._custom_format.initialize(a_controldir, shared=shared)
537
1077
        elif self._creating_bzrdir is not None:
538
1078
            # Use the format that the repository we were created to back
539
1079
            # has.
540
1080
            prior_repo = self._creating_bzrdir.open_repository()
541
1081
            prior_repo._ensure_real()
542
1082
            result = prior_repo._real_repository._format.initialize(
543
 
                a_bzrdir, shared=shared)
 
1083
                a_controldir, shared=shared)
544
1084
        else:
545
1085
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
1086
            # support remote initialization.
547
1087
            # We delegate to a real object at this point (as RemoteBzrDir
548
1088
            # delegate to the repository format which would lead to infinite
549
 
            # recursion if we just called a_bzrdir.create_repository.
550
 
            a_bzrdir._ensure_real()
551
 
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
1089
            # recursion if we just called a_controldir.create_repository.
 
1090
            a_controldir._ensure_real()
 
1091
            result = a_controldir._real_bzrdir.create_repository(shared=shared)
552
1092
        if not isinstance(result, RemoteRepository):
553
 
            return self.open(a_bzrdir)
 
1093
            return self.open(a_controldir)
554
1094
        else:
555
1095
            return result
556
1096
 
557
 
    def initialize(self, a_bzrdir, shared=False):
 
1097
    def initialize(self, a_controldir, shared=False):
558
1098
        # Being asked to create on a non RemoteBzrDir:
559
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
560
 
            return self._vfs_initialize(a_bzrdir, shared)
561
 
        medium = a_bzrdir._client._medium
 
1099
        if not isinstance(a_controldir, RemoteBzrDir):
 
1100
            return self._vfs_initialize(a_controldir, shared)
 
1101
        medium = a_controldir._client._medium
562
1102
        if medium._is_remote_before((1, 13)):
563
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1103
            return self._vfs_initialize(a_controldir, shared)
564
1104
        # Creating on a remote bzr dir.
565
1105
        # 1) get the network name to use.
566
1106
        if self._custom_format:
568
1108
        elif self._network_name:
569
1109
            network_name = self._network_name
570
1110
        else:
571
 
            # Select the current bzrlib default and ask for that.
572
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
1111
            # Select the current breezy default and ask for that.
 
1112
            reference_bzrdir_format = controldir.format_registry.get('default')()
573
1113
            reference_format = reference_bzrdir_format.repository_format
574
1114
            network_name = reference_format.network_name()
575
1115
        # 2) try direct creation via RPC
576
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
577
 
        verb = 'BzrDir.create_repository'
 
1116
        path = a_controldir._path_for_remote_call(a_controldir._client)
 
1117
        verb = b'BzrDir.create_repository'
578
1118
        if shared:
579
 
            shared_str = 'True'
 
1119
            shared_str = b'True'
580
1120
        else:
581
 
            shared_str = 'False'
 
1121
            shared_str = b'False'
582
1122
        try:
583
 
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
1123
            response = a_controldir._call(verb, path, network_name, shared_str)
584
1124
        except errors.UnknownSmartMethod:
585
1125
            # Fallback - use vfs methods
586
1126
            medium._remember_remote_is_before((1, 13))
587
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1127
            return self._vfs_initialize(a_controldir, shared)
588
1128
        else:
589
1129
            # Turn the response into a RemoteRepository object.
590
1130
            format = response_tuple_to_repo_format(response[1:])
591
1131
            # Used to support creating a real format instance when needed.
592
 
            format._creating_bzrdir = a_bzrdir
593
 
            remote_repo = RemoteRepository(a_bzrdir, format)
 
1132
            format._creating_bzrdir = a_controldir
 
1133
            remote_repo = RemoteRepository(a_controldir, format)
594
1134
            format._creating_repo = remote_repo
595
1135
            return remote_repo
596
1136
 
597
 
    def open(self, a_bzrdir):
598
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
599
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
 
        return a_bzrdir.open_repository()
 
1137
    def open(self, a_controldir):
 
1138
        if not isinstance(a_controldir, RemoteBzrDir):
 
1139
            raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
 
1140
        return a_controldir.open_repository()
601
1141
 
602
1142
    def _ensure_real(self):
603
1143
        if self._custom_format is None:
604
 
            self._custom_format = repository.network_format_registry.get(
605
 
                self._network_name)
 
1144
            try:
 
1145
                self._custom_format = _mod_repository.network_format_registry.get(
 
1146
                    self._network_name)
 
1147
            except KeyError:
 
1148
                raise errors.UnknownFormatError(kind='repository',
 
1149
                    format=self._network_name)
606
1150
 
607
1151
    @property
608
1152
    def _fetch_order(self):
643
1187
        return self._custom_format._serializer
644
1188
 
645
1189
 
646
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
647
 
    bzrdir.ControlComponent):
 
1190
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
 
1191
        lock._RelockDebugMixin):
648
1192
    """Repository accessed over rpc.
649
1193
 
650
1194
    For the moment most operations are performed using local transport-backed
666
1210
            self._real_repository = real_repository
667
1211
        else:
668
1212
            self._real_repository = None
669
 
        self.bzrdir = remote_bzrdir
 
1213
        self.controldir = remote_bzrdir
670
1214
        if _client is None:
671
1215
            self._client = remote_bzrdir._client
672
1216
        else:
674
1218
        self._format = format
675
1219
        self._lock_mode = None
676
1220
        self._lock_token = None
 
1221
        self._write_group_tokens = None
677
1222
        self._lock_count = 0
678
1223
        self._leave_lock = False
679
1224
        # Cache of revision parents; misses are cached during read locks, and
689
1234
        self._reconcile_does_inventory_gc = False
690
1235
        self._reconcile_fixes_text_parents = False
691
1236
        self._reconcile_backsup_inventory = False
692
 
        self.base = self.bzrdir.transport.base
 
1237
        self.base = self.controldir.transport.base
693
1238
        # Additional places to query for data.
694
1239
        self._fallback_repositories = []
695
1240
 
696
1241
    @property
697
1242
    def user_transport(self):
698
 
        return self.bzrdir.user_transport
 
1243
        return self.controldir.user_transport
699
1244
 
700
1245
    @property
701
1246
    def control_transport(self):
702
1247
        # XXX: Normally you shouldn't directly get at the remote repository
703
1248
        # transport, but I'm not sure it's worth making this method
704
1249
        # optional -- mbp 2010-04-21
705
 
        return self.bzrdir.get_repository_transport(None)
706
 
        
 
1250
        return self.controldir.get_repository_transport(None)
 
1251
 
707
1252
    def __str__(self):
708
1253
        return "%s(%s)" % (self.__class__.__name__, self.base)
709
1254
 
719
1264
 
720
1265
        :param suppress_errors: see Repository.abort_write_group.
721
1266
        """
722
 
        self._ensure_real()
723
 
        return self._real_repository.abort_write_group(
724
 
            suppress_errors=suppress_errors)
 
1267
        if self._real_repository:
 
1268
            self._ensure_real()
 
1269
            return self._real_repository.abort_write_group(
 
1270
                suppress_errors=suppress_errors)
 
1271
        if not self.is_in_write_group():
 
1272
            if suppress_errors:
 
1273
                mutter('(suppressed) not in write group')
 
1274
                return
 
1275
            raise errors.BzrError("not in write group")
 
1276
        path = self.controldir._path_for_remote_call(self._client)
 
1277
        try:
 
1278
            response = self._call(b'Repository.abort_write_group', path,
 
1279
                self._lock_token,
 
1280
                [token.encode('utf-8') for token in self._write_group_tokens])
 
1281
        except Exception as exc:
 
1282
            self._write_group = None
 
1283
            if not suppress_errors:
 
1284
                raise
 
1285
            mutter('abort_write_group failed')
 
1286
            log_exception_quietly()
 
1287
            note(gettext('bzr: ERROR (ignored): %s'), exc)
 
1288
        else:
 
1289
            if response != (b'ok', ):
 
1290
                raise errors.UnexpectedSmartServerResponse(response)
 
1291
            self._write_group_tokens = None
725
1292
 
726
1293
    @property
727
1294
    def chk_bytes(self):
741
1308
        for older plugins that don't use e.g. the CommitBuilder
742
1309
        facility.
743
1310
        """
744
 
        self._ensure_real()
745
 
        return self._real_repository.commit_write_group()
 
1311
        if self._real_repository:
 
1312
            self._ensure_real()
 
1313
            return self._real_repository.commit_write_group()
 
1314
        if not self.is_in_write_group():
 
1315
            raise errors.BzrError("not in write group")
 
1316
        path = self.controldir._path_for_remote_call(self._client)
 
1317
        response = self._call(b'Repository.commit_write_group', path,
 
1318
            self._lock_token, [token.encode('utf-8') for token in self._write_group_tokens])
 
1319
        if response != (b'ok', ):
 
1320
            raise errors.UnexpectedSmartServerResponse(response)
 
1321
        self._write_group_tokens = None
 
1322
        # Refresh data after writing to the repository.
 
1323
        self.refresh_data()
746
1324
 
747
1325
    def resume_write_group(self, tokens):
748
 
        self._ensure_real()
749
 
        return self._real_repository.resume_write_group(tokens)
 
1326
        if self._real_repository:
 
1327
            return self._real_repository.resume_write_group(tokens)
 
1328
        path = self.controldir._path_for_remote_call(self._client)
 
1329
        try:
 
1330
            response = self._call(b'Repository.check_write_group', path,
 
1331
               self._lock_token, [token.encode('utf-8') for token in tokens])
 
1332
        except errors.UnknownSmartMethod:
 
1333
            self._ensure_real()
 
1334
            return self._real_repository.resume_write_group(tokens)
 
1335
        if response != (b'ok', ):
 
1336
            raise errors.UnexpectedSmartServerResponse(response)
 
1337
        self._write_group_tokens = tokens
750
1338
 
751
1339
    def suspend_write_group(self):
752
 
        self._ensure_real()
753
 
        return self._real_repository.suspend_write_group()
 
1340
        if self._real_repository:
 
1341
            return self._real_repository.suspend_write_group()
 
1342
        ret = self._write_group_tokens or []
 
1343
        self._write_group_tokens = None
 
1344
        return ret
754
1345
 
755
1346
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
756
1347
        self._ensure_real()
764
1355
 
765
1356
    def get_rev_id_for_revno(self, revno, known_pair):
766
1357
        """See Repository.get_rev_id_for_revno."""
767
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1358
        path = self.controldir._path_for_remote_call(self._client)
768
1359
        try:
769
1360
            if self._client._medium._is_remote_before((1, 17)):
770
1361
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
771
1362
            response = self._call(
772
 
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
1363
                b'Repository.get_rev_id_for_revno', path, revno, known_pair)
773
1364
        except errors.UnknownSmartMethod:
774
1365
            self._client._medium._remember_remote_is_before((1, 17))
775
1366
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
776
 
        if response[0] == 'ok':
 
1367
        if response[0] == b'ok':
777
1368
            return True, response[1]
778
 
        elif response[0] == 'history-incomplete':
 
1369
        elif response[0] == b'history-incomplete':
779
1370
            known_pair = response[1:3]
780
1371
            for fallback in self._fallback_repositories:
781
1372
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
807
1398
                warning('VFS Repository access triggered\n%s',
808
1399
                    ''.join(traceback.format_stack()))
809
1400
            self._unstacked_provider.missing_keys.clear()
810
 
            self.bzrdir._ensure_real()
 
1401
            self.controldir._ensure_real()
811
1402
            self._set_real_repository(
812
 
                self.bzrdir._real_bzrdir.open_repository())
 
1403
                self.controldir._real_bzrdir.open_repository())
813
1404
 
814
1405
    def _translate_error(self, err, **context):
815
 
        self.bzrdir._translate_error(err, repository=self, **context)
 
1406
        self.controldir._translate_error(err, repository=self, **context)
816
1407
 
817
1408
    def find_text_key_references(self):
818
1409
        """Find the text key references within the repository.
819
1410
 
820
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
821
 
        revision_ids. Each altered file-ids has the exact revision_ids that
822
 
        altered it listed explicitly.
823
1411
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
1412
            to whether they were referred to by the inventory of the
825
1413
            revision_id that they contain. The inventory texts from all present
842
1430
    def _get_revision_graph(self, revision_id):
843
1431
        """Private method for using with old (< 1.2) servers to fallback."""
844
1432
        if revision_id is None:
845
 
            revision_id = ''
846
 
        elif revision.is_null(revision_id):
 
1433
            revision_id = b''
 
1434
        elif _mod_revision.is_null(revision_id):
847
1435
            return {}
848
1436
 
849
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1437
        path = self.controldir._path_for_remote_call(self._client)
850
1438
        response = self._call_expecting_body(
851
 
            'Repository.get_revision_graph', path, revision_id)
 
1439
            b'Repository.get_revision_graph', path, revision_id)
852
1440
        response_tuple, response_handler = response
853
 
        if response_tuple[0] != 'ok':
 
1441
        if response_tuple[0] != b'ok':
854
1442
            raise errors.UnexpectedSmartServerResponse(response_tuple)
855
1443
        coded = response_handler.read_body_bytes()
856
 
        if coded == '':
 
1444
        if coded == b'':
857
1445
            # no revisions in this repository!
858
1446
            return {}
859
 
        lines = coded.split('\n')
 
1447
        lines = coded.split(b'\n')
860
1448
        revision_graph = {}
861
1449
        for line in lines:
862
1450
            d = tuple(line.split())
872
1460
        """Return a source for streaming from this repository."""
873
1461
        return RemoteStreamSource(self, to_format)
874
1462
 
875
 
    @needs_read_lock
 
1463
    def get_file_graph(self):
 
1464
        with self.lock_read():
 
1465
            return graph.Graph(self.texts)
 
1466
 
876
1467
    def has_revision(self, revision_id):
877
1468
        """True if this repository has a copy of the revision."""
878
 
        # Copy of bzrlib.repository.Repository.has_revision
879
 
        return revision_id in self.has_revisions((revision_id,))
 
1469
        # Copy of breezy.repository.Repository.has_revision
 
1470
        with self.lock_read():
 
1471
            return revision_id in self.has_revisions((revision_id,))
880
1472
 
881
 
    @needs_read_lock
882
1473
    def has_revisions(self, revision_ids):
883
1474
        """Probe to find out the presence of multiple revisions.
884
1475
 
885
1476
        :param revision_ids: An iterable of revision_ids.
886
1477
        :return: A set of the revision_ids that were present.
887
1478
        """
888
 
        # Copy of bzrlib.repository.Repository.has_revisions
889
 
        parent_map = self.get_parent_map(revision_ids)
890
 
        result = set(parent_map)
891
 
        if _mod_revision.NULL_REVISION in revision_ids:
892
 
            result.add(_mod_revision.NULL_REVISION)
893
 
        return result
 
1479
        with self.lock_read():
 
1480
            # Copy of breezy.repository.Repository.has_revisions
 
1481
            parent_map = self.get_parent_map(revision_ids)
 
1482
            result = set(parent_map)
 
1483
            if _mod_revision.NULL_REVISION in revision_ids:
 
1484
                result.add(_mod_revision.NULL_REVISION)
 
1485
            return result
894
1486
 
895
1487
    def _has_same_fallbacks(self, other_repo):
896
1488
        """Returns true if the repositories have the same fallbacks."""
897
1489
        # XXX: copied from Repository; it should be unified into a base class
898
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
1490
        # <https://bugs.launchpad.net/bzr/+bug/401622>
899
1491
        my_fb = self._fallback_repositories
900
1492
        other_fb = other_repo._fallback_repositories
901
1493
        if len(my_fb) != len(other_fb):
910
1502
        # one; unfortunately the tests rely on slightly different behaviour at
911
1503
        # present -- mbp 20090710
912
1504
        return (self.__class__ is other.__class__ and
913
 
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
1505
                self.controldir.transport.base == other.controldir.transport.base)
914
1506
 
915
1507
    def get_graph(self, other_repository=None):
916
1508
        """Return the graph for this repository format"""
917
1509
        parents_provider = self._make_parents_provider(other_repository)
918
1510
        return graph.Graph(parents_provider)
919
1511
 
920
 
    @needs_read_lock
921
1512
    def get_known_graph_ancestry(self, revision_ids):
922
1513
        """Return the known graph for a set of revision ids and their ancestors.
923
1514
        """
924
 
        st = static_tuple.StaticTuple
925
 
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
926
 
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
927
 
        return graph.GraphThunkIdsToKeys(known_graph)
 
1515
        with self.lock_read():
 
1516
            revision_graph = dict(((key, value) for key, value in
 
1517
                self.get_graph().iter_ancestry(revision_ids) if value is not None))
 
1518
            revision_graph = _mod_repository._strip_NULL_ghosts(revision_graph)
 
1519
            return graph.KnownGraph(revision_graph)
928
1520
 
929
1521
    def gather_stats(self, revid=None, committers=None):
930
1522
        """See Repository.gather_stats()."""
931
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1523
        path = self.controldir._path_for_remote_call(self._client)
932
1524
        # revid can be None to indicate no revisions, not just NULL_REVISION
933
 
        if revid is None or revision.is_null(revid):
934
 
            fmt_revid = ''
 
1525
        if revid is None or _mod_revision.is_null(revid):
 
1526
            fmt_revid = b''
935
1527
        else:
936
1528
            fmt_revid = revid
937
1529
        if committers is None or not committers:
938
 
            fmt_committers = 'no'
 
1530
            fmt_committers = b'no'
939
1531
        else:
940
 
            fmt_committers = 'yes'
 
1532
            fmt_committers = b'yes'
941
1533
        response_tuple, response_handler = self._call_expecting_body(
942
 
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
943
 
        if response_tuple[0] != 'ok':
 
1534
            b'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
1535
        if response_tuple[0] != b'ok':
944
1536
            raise errors.UnexpectedSmartServerResponse(response_tuple)
945
1537
 
946
1538
        body = response_handler.read_body_bytes()
947
1539
        result = {}
948
 
        for line in body.split('\n'):
 
1540
        for line in body.split(b'\n'):
949
1541
            if not line:
950
1542
                continue
951
 
            key, val_text = line.split(':')
 
1543
            key, val_text = line.split(b':')
 
1544
            key = key.decode('ascii')
952
1545
            if key in ('revisions', 'size', 'committers'):
953
1546
                result[key] = int(val_text)
954
1547
            elif key in ('firstrev', 'latestrev'):
955
 
                values = val_text.split(' ')[1:]
956
 
                result[key] = (float(values[0]), long(values[1]))
 
1548
                values = val_text.split(b' ')[1:]
 
1549
                result[key] = (float(values[0]), int(values[1]))
957
1550
 
958
1551
        return result
959
1552
 
965
1558
 
966
1559
    def get_physical_lock_status(self):
967
1560
        """See Repository.get_physical_lock_status()."""
968
 
        # should be an API call to the server.
969
 
        self._ensure_real()
970
 
        return self._real_repository.get_physical_lock_status()
 
1561
        path = self.controldir._path_for_remote_call(self._client)
 
1562
        try:
 
1563
            response = self._call(b'Repository.get_physical_lock_status', path)
 
1564
        except errors.UnknownSmartMethod:
 
1565
            self._ensure_real()
 
1566
            return self._real_repository.get_physical_lock_status()
 
1567
        if response[0] not in (b'yes', b'no'):
 
1568
            raise errors.UnexpectedSmartServerResponse(response)
 
1569
        return (response[0] == b'yes')
971
1570
 
972
1571
    def is_in_write_group(self):
973
1572
        """Return True if there is an open write group.
974
1573
 
975
1574
        write groups are only applicable locally for the smart server..
976
1575
        """
 
1576
        if self._write_group_tokens is not None:
 
1577
            return True
977
1578
        if self._real_repository:
978
1579
            return self._real_repository.is_in_write_group()
979
1580
 
982
1583
 
983
1584
    def is_shared(self):
984
1585
        """See Repository.is_shared()."""
985
 
        path = self.bzrdir._path_for_remote_call(self._client)
986
 
        response = self._call('Repository.is_shared', path)
987
 
        if response[0] not in ('yes', 'no'):
 
1586
        path = self.controldir._path_for_remote_call(self._client)
 
1587
        response = self._call(b'Repository.is_shared', path)
 
1588
        if response[0] not in (b'yes', b'no'):
988
1589
            raise SmartProtocolError('unexpected response code %s' % (response,))
989
 
        return response[0] == 'yes'
 
1590
        return response[0] == b'yes'
990
1591
 
991
1592
    def is_write_locked(self):
992
1593
        return self._lock_mode == 'w'
997
1598
        pass
998
1599
 
999
1600
    def lock_read(self):
 
1601
        """Lock the repository for read operations.
 
1602
 
 
1603
        :return: A breezy.lock.LogicalLockResult.
 
1604
        """
1000
1605
        # wrong eventually - want a local lock cache context
1001
1606
        if not self._lock_mode:
1002
1607
            self._note_lock('r')
1009
1614
                repo.lock_read()
1010
1615
        else:
1011
1616
            self._lock_count += 1
 
1617
        return lock.LogicalLockResult(self.unlock)
1012
1618
 
1013
1619
    def _remote_lock_write(self, token):
1014
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1620
        path = self.controldir._path_for_remote_call(self._client)
1015
1621
        if token is None:
1016
 
            token = ''
 
1622
            token = b''
1017
1623
        err_context = {'token': token}
1018
 
        response = self._call('Repository.lock_write', path, token,
 
1624
        response = self._call(b'Repository.lock_write', path, token,
1019
1625
                              **err_context)
1020
 
        if response[0] == 'ok':
 
1626
        if response[0] == b'ok':
1021
1627
            ok, token = response
1022
1628
            return token
1023
1629
        else:
1054
1660
            raise errors.ReadOnlyError(self)
1055
1661
        else:
1056
1662
            self._lock_count += 1
1057
 
        return self._lock_token or None
 
1663
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1058
1664
 
1059
1665
    def leave_lock_in_place(self):
1060
1666
        if not self._lock_token:
1109
1715
            self._real_repository.lock_write(self._lock_token)
1110
1716
        elif self._lock_mode == 'r':
1111
1717
            self._real_repository.lock_read()
 
1718
        if self._write_group_tokens is not None:
 
1719
            # if we are already in a write group, resume it
 
1720
            self._real_repository.resume_write_group(self._write_group_tokens)
 
1721
            self._write_group_tokens = None
1112
1722
 
1113
1723
    def start_write_group(self):
1114
1724
        """Start a write group on the decorated repository.
1118
1728
        for older plugins that don't use e.g. the CommitBuilder
1119
1729
        facility.
1120
1730
        """
1121
 
        self._ensure_real()
1122
 
        return self._real_repository.start_write_group()
 
1731
        if self._real_repository:
 
1732
            self._ensure_real()
 
1733
            return self._real_repository.start_write_group()
 
1734
        if not self.is_write_locked():
 
1735
            raise errors.NotWriteLocked(self)
 
1736
        if self._write_group_tokens is not None:
 
1737
            raise errors.BzrError('already in a write group')
 
1738
        path = self.controldir._path_for_remote_call(self._client)
 
1739
        try:
 
1740
            response = self._call(b'Repository.start_write_group', path,
 
1741
                self._lock_token)
 
1742
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
 
1743
            self._ensure_real()
 
1744
            return self._real_repository.start_write_group()
 
1745
        if response[0] != b'ok':
 
1746
            raise errors.UnexpectedSmartServerResponse(response)
 
1747
        self._write_group_tokens = [token.decode('utf-8') for token in response[1]]
1123
1748
 
1124
1749
    def _unlock(self, token):
1125
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1750
        path = self.controldir._path_for_remote_call(self._client)
1126
1751
        if not token:
1127
1752
            # with no token the remote repository is not persistently locked.
1128
1753
            return
1129
1754
        err_context = {'token': token}
1130
 
        response = self._call('Repository.unlock', path, token,
 
1755
        response = self._call(b'Repository.unlock', path, token,
1131
1756
                              **err_context)
1132
 
        if response == ('ok',):
 
1757
        if response == (b'ok',):
1133
1758
            return
1134
1759
        else:
1135
1760
            raise errors.UnexpectedSmartServerResponse(response)
1152
1777
            # This is just to let the _real_repository stay up to date.
1153
1778
            if self._real_repository is not None:
1154
1779
                self._real_repository.unlock()
 
1780
            elif self._write_group_tokens is not None:
 
1781
                self.abort_write_group()
1155
1782
        finally:
1156
1783
            # The rpc-level lock should be released even if there was a
1157
1784
            # problem releasing the vfs-based lock.
1169
1796
 
1170
1797
    def break_lock(self):
1171
1798
        # should hand off to the network
1172
 
        self._ensure_real()
1173
 
        return self._real_repository.break_lock()
 
1799
        path = self.controldir._path_for_remote_call(self._client)
 
1800
        try:
 
1801
            response = self._call(b"Repository.break_lock", path)
 
1802
        except errors.UnknownSmartMethod:
 
1803
            self._ensure_real()
 
1804
            return self._real_repository.break_lock()
 
1805
        if response != (b'ok',):
 
1806
            raise errors.UnexpectedSmartServerResponse(response)
1174
1807
 
1175
1808
    def _get_tarball(self, compression):
1176
1809
        """Return a TemporaryFile containing a repository tarball.
1178
1811
        Returns None if the server does not support sending tarballs.
1179
1812
        """
1180
1813
        import tempfile
1181
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1814
        path = self.controldir._path_for_remote_call(self._client)
1182
1815
        try:
1183
1816
            response, protocol = self._call_expecting_body(
1184
 
                'Repository.tarball', path, compression)
 
1817
                b'Repository.tarball', path, compression.encode('ascii'))
1185
1818
        except errors.UnknownSmartMethod:
1186
1819
            protocol.cancel_read_body()
1187
1820
            return None
1188
 
        if response[0] == 'ok':
 
1821
        if response[0] == b'ok':
1189
1822
            # Extract the tarball and return it
1190
1823
            t = tempfile.NamedTemporaryFile()
1191
1824
            # TODO: rpc layer should read directly into it...
1195
1828
        raise errors.UnexpectedSmartServerResponse(response)
1196
1829
 
1197
1830
    def sprout(self, to_bzrdir, revision_id=None):
1198
 
        # TODO: Option to control what format is created?
1199
 
        self._ensure_real()
1200
 
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
1201
 
                                                             shared=False)
1202
 
        dest_repo.fetch(self, revision_id=revision_id)
 
1831
        """Create a descendent repository for new development.
 
1832
 
 
1833
        Unlike clone, this does not copy the settings of the repository.
 
1834
        """
 
1835
        with self.lock_read():
 
1836
            dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
1837
            dest_repo.fetch(self, revision_id=revision_id)
 
1838
            return dest_repo
 
1839
 
 
1840
    def _create_sprouting_repo(self, a_controldir, shared):
 
1841
        if not isinstance(a_controldir._format, self.controldir._format.__class__):
 
1842
            # use target default format.
 
1843
            dest_repo = a_controldir.create_repository()
 
1844
        else:
 
1845
            # Most control formats need the repository to be specifically
 
1846
            # created, but on some old all-in-one formats it's not needed
 
1847
            try:
 
1848
                dest_repo = self._format.initialize(a_controldir, shared=shared)
 
1849
            except errors.UninitializableFormat:
 
1850
                dest_repo = a_controldir.open_repository()
1203
1851
        return dest_repo
1204
1852
 
1205
1853
    ### These methods are just thin shims to the VFS object for now.
1206
1854
 
1207
1855
    def revision_tree(self, revision_id):
1208
 
        self._ensure_real()
1209
 
        return self._real_repository.revision_tree(revision_id)
 
1856
        with self.lock_read():
 
1857
            revision_id = _mod_revision.ensure_null(revision_id)
 
1858
            if revision_id == _mod_revision.NULL_REVISION:
 
1859
                return InventoryRevisionTree(self,
 
1860
                    Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
1861
            else:
 
1862
                return list(self.revision_trees([revision_id]))[0]
1210
1863
 
1211
1864
    def get_serializer_format(self):
1212
 
        self._ensure_real()
1213
 
        return self._real_repository.get_serializer_format()
 
1865
        path = self.controldir._path_for_remote_call(self._client)
 
1866
        try:
 
1867
            response = self._call(b'VersionedFileRepository.get_serializer_format',
 
1868
                path)
 
1869
        except errors.UnknownSmartMethod:
 
1870
            self._ensure_real()
 
1871
            return self._real_repository.get_serializer_format()
 
1872
        if response[0] != b'ok':
 
1873
            raise errors.UnexpectedSmartServerResponse(response)
 
1874
        return response[1]
1214
1875
 
1215
1876
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
1877
                           timezone=None, committer=None, revprops=None,
1217
 
                           revision_id=None):
1218
 
        # FIXME: It ought to be possible to call this without immediately
1219
 
        # triggering _ensure_real.  For now it's the easiest thing to do.
1220
 
        self._ensure_real()
1221
 
        real_repo = self._real_repository
1222
 
        builder = real_repo.get_commit_builder(branch, parents,
1223
 
                config, timestamp=timestamp, timezone=timezone,
1224
 
                committer=committer, revprops=revprops, revision_id=revision_id)
1225
 
        return builder
 
1878
                           revision_id=None, lossy=False):
 
1879
        """Obtain a CommitBuilder for this repository.
 
1880
 
 
1881
        :param branch: Branch to commit to.
 
1882
        :param parents: Revision ids of the parents of the new revision.
 
1883
        :param config: Configuration to use.
 
1884
        :param timestamp: Optional timestamp recorded for commit.
 
1885
        :param timezone: Optional timezone for timestamp.
 
1886
        :param committer: Optional committer to set for commit.
 
1887
        :param revprops: Optional dictionary of revision properties.
 
1888
        :param revision_id: Optional revision id.
 
1889
        :param lossy: Whether to discard data that can not be natively
 
1890
            represented, when pushing to a foreign VCS
 
1891
        """
 
1892
        if self._fallback_repositories and not self._format.supports_chks:
 
1893
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1894
                " in pre-2a formats. See "
 
1895
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
1896
        commit_builder_kls = vf_repository.VersionedFileCommitBuilder
 
1897
        result = commit_builder_kls(self, parents, config,
 
1898
            timestamp, timezone, committer, revprops, revision_id,
 
1899
            lossy)
 
1900
        self.start_write_group()
 
1901
        return result
1226
1902
 
1227
1903
    def add_fallback_repository(self, repository):
1228
1904
        """Add a repository to use for looking up data not held locally.
1235
1911
        # We need to accumulate additional repositories here, to pass them in
1236
1912
        # on various RPC's.
1237
1913
        #
 
1914
        # Make the check before we lock: this raises an exception.
 
1915
        self._check_fallback_repository(repository)
1238
1916
        if self.is_locked():
1239
1917
            # We will call fallback.unlock() when we transition to the unlocked
1240
1918
            # state, so always add a lock here. If a caller passes us a locked
1241
1919
            # repository, they are responsible for unlocking it later.
1242
1920
            repository.lock_read()
1243
 
        self._check_fallback_repository(repository)
1244
1921
        self._fallback_repositories.append(repository)
1245
1922
        # If self._real_repository was parameterised already (e.g. because a
1246
1923
        # _real_branch had its get_stacked_on_url method called), then the
1272
1949
            delta, new_revision_id, parents, basis_inv=basis_inv,
1273
1950
            propagate_caches=propagate_caches)
1274
1951
 
1275
 
    def add_revision(self, rev_id, rev, inv=None, config=None):
1276
 
        self._ensure_real()
1277
 
        return self._real_repository.add_revision(
1278
 
            rev_id, rev, inv=inv, config=config)
1279
 
 
1280
 
    @needs_read_lock
 
1952
    def add_revision(self, revision_id, rev, inv=None):
 
1953
        _mod_revision.check_not_reserved_id(revision_id)
 
1954
        key = (revision_id,)
 
1955
        # check inventory present
 
1956
        if not self.inventories.get_parent_map([key]):
 
1957
            if inv is None:
 
1958
                raise errors.WeaveRevisionNotPresent(revision_id,
 
1959
                                                     self.inventories)
 
1960
            else:
 
1961
                # yes, this is not suitable for adding with ghosts.
 
1962
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
 
1963
                                                        rev.parent_ids)
 
1964
        else:
 
1965
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
 
1966
        self._add_revision(rev)
 
1967
 
 
1968
    def _add_revision(self, rev):
 
1969
        if self._real_repository is not None:
 
1970
            return self._real_repository._add_revision(rev)
 
1971
        text = self._serializer.write_revision_to_string(rev)
 
1972
        key = (rev.revision_id,)
 
1973
        parents = tuple((parent,) for parent in rev.parent_ids)
 
1974
        self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
 
1975
            [('revisions', [FulltextContentFactory(key, parents, None, text)])],
 
1976
            self._format, self._write_group_tokens)
 
1977
 
1281
1978
    def get_inventory(self, revision_id):
 
1979
        with self.lock_read():
 
1980
            return list(self.iter_inventories([revision_id]))[0]
 
1981
 
 
1982
    def _iter_inventories_rpc(self, revision_ids, ordering):
 
1983
        if ordering is None:
 
1984
            ordering = 'unordered'
 
1985
        path = self.controldir._path_for_remote_call(self._client)
 
1986
        body = b"\n".join(revision_ids)
 
1987
        response_tuple, response_handler = (
 
1988
            self._call_with_body_bytes_expecting_body(
 
1989
                b"VersionedFileRepository.get_inventories",
 
1990
                (path, ordering.encode('ascii')), body))
 
1991
        if response_tuple[0] != b"ok":
 
1992
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1993
        deserializer = inventory_delta.InventoryDeltaDeserializer()
 
1994
        byte_stream = response_handler.read_streamed_body()
 
1995
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
 
1996
        if decoded is None:
 
1997
            # no results whatsoever
 
1998
            return
 
1999
        src_format, stream = decoded
 
2000
        if src_format.network_name() != self._format.network_name():
 
2001
            raise AssertionError(
 
2002
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
2003
                src_format.network_name(), self._format.network_name()))
 
2004
        # ignore the src format, it's not really relevant
 
2005
        prev_inv = Inventory(root_id=None,
 
2006
            revision_id=_mod_revision.NULL_REVISION)
 
2007
        # there should be just one substream, with inventory deltas
 
2008
        try:
 
2009
            substream_kind, substream = next(stream)
 
2010
        except StopIteration:
 
2011
            return
 
2012
        if substream_kind != "inventory-deltas":
 
2013
            raise AssertionError(
 
2014
                 "Unexpected stream %r received" % substream_kind)
 
2015
        for record in substream:
 
2016
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
 
2017
                deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
 
2018
            if parent_id != prev_inv.revision_id:
 
2019
                raise AssertionError("invalid base %r != %r" % (parent_id,
 
2020
                    prev_inv.revision_id))
 
2021
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
 
2022
            yield inv, inv.revision_id
 
2023
            prev_inv = inv
 
2024
 
 
2025
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
1282
2026
        self._ensure_real()
1283
 
        return self._real_repository.get_inventory(revision_id)
 
2027
        return self._real_repository._iter_inventories(revision_ids, ordering)
1284
2028
 
1285
2029
    def iter_inventories(self, revision_ids, ordering=None):
1286
 
        self._ensure_real()
1287
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
1288
 
 
1289
 
    @needs_read_lock
 
2030
        """Get many inventories by revision_ids.
 
2031
 
 
2032
        This will buffer some or all of the texts used in constructing the
 
2033
        inventories in memory, but will only parse a single inventory at a
 
2034
        time.
 
2035
 
 
2036
        :param revision_ids: The expected revision ids of the inventories.
 
2037
        :param ordering: optional ordering, e.g. 'topological'.  If not
 
2038
            specified, the order of revision_ids will be preserved (by
 
2039
            buffering if necessary).
 
2040
        :return: An iterator of inventories.
 
2041
        """
 
2042
        if ((None in revision_ids)
 
2043
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
2044
            raise ValueError('cannot get null revision inventory')
 
2045
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
2046
            if inv is None:
 
2047
                raise errors.NoSuchRevision(self, revid)
 
2048
            yield inv
 
2049
 
 
2050
    def _iter_inventories(self, revision_ids, ordering=None):
 
2051
        if len(revision_ids) == 0:
 
2052
            return
 
2053
        missing = set(revision_ids)
 
2054
        if ordering is None:
 
2055
            order_as_requested = True
 
2056
            invs = {}
 
2057
            order = list(revision_ids)
 
2058
            order.reverse()
 
2059
            next_revid = order.pop()
 
2060
        else:
 
2061
            order_as_requested = False
 
2062
            if ordering != 'unordered' and self._fallback_repositories:
 
2063
                raise ValueError('unsupported ordering %r' % ordering)
 
2064
        iter_inv_fns = [self._iter_inventories_rpc] + [
 
2065
            fallback._iter_inventories for fallback in
 
2066
            self._fallback_repositories]
 
2067
        try:
 
2068
            for iter_inv in iter_inv_fns:
 
2069
                request = [revid for revid in revision_ids if revid in missing]
 
2070
                for inv, revid in iter_inv(request, ordering):
 
2071
                    if inv is None:
 
2072
                        continue
 
2073
                    missing.remove(inv.revision_id)
 
2074
                    if ordering != 'unordered':
 
2075
                        invs[revid] = inv
 
2076
                    else:
 
2077
                        yield inv, revid
 
2078
                if order_as_requested:
 
2079
                    # Yield as many results as we can while preserving order.
 
2080
                    while next_revid in invs:
 
2081
                        inv = invs.pop(next_revid)
 
2082
                        yield inv, inv.revision_id
 
2083
                        try:
 
2084
                            next_revid = order.pop()
 
2085
                        except IndexError:
 
2086
                            # We still want to fully consume the stream, just
 
2087
                            # in case it is not actually finished at this point
 
2088
                            next_revid = None
 
2089
                            break
 
2090
        except errors.UnknownSmartMethod:
 
2091
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
 
2092
                yield inv, revid
 
2093
            return
 
2094
        # Report missing
 
2095
        if order_as_requested:
 
2096
            if next_revid is not None:
 
2097
                yield None, next_revid
 
2098
            while order:
 
2099
                revid = order.pop()
 
2100
                yield invs.get(revid), revid
 
2101
        else:
 
2102
            while missing:
 
2103
                yield None, missing.pop()
 
2104
 
1290
2105
    def get_revision(self, revision_id):
1291
 
        self._ensure_real()
1292
 
        return self._real_repository.get_revision(revision_id)
 
2106
        with self.lock_read():
 
2107
            return self.get_revisions([revision_id])[0]
1293
2108
 
1294
2109
    def get_transaction(self):
1295
2110
        self._ensure_real()
1296
2111
        return self._real_repository.get_transaction()
1297
2112
 
1298
 
    @needs_read_lock
1299
 
    def clone(self, a_bzrdir, revision_id=None):
1300
 
        self._ensure_real()
1301
 
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
2113
    def clone(self, a_controldir, revision_id=None):
 
2114
        with self.lock_read():
 
2115
            dest_repo = self._create_sprouting_repo(
 
2116
                a_controldir, shared=self.is_shared())
 
2117
            self.copy_content_into(dest_repo, revision_id)
 
2118
            return dest_repo
1302
2119
 
1303
2120
    def make_working_trees(self):
1304
2121
        """See Repository.make_working_trees"""
1305
 
        self._ensure_real()
1306
 
        return self._real_repository.make_working_trees()
 
2122
        path = self.controldir._path_for_remote_call(self._client)
 
2123
        try:
 
2124
            response = self._call(b'Repository.make_working_trees', path)
 
2125
        except errors.UnknownSmartMethod:
 
2126
            self._ensure_real()
 
2127
            return self._real_repository.make_working_trees()
 
2128
        if response[0] not in (b'yes', b'no'):
 
2129
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2130
        return response[0] == b'yes'
1307
2131
 
1308
2132
    def refresh_data(self):
1309
 
        """Re-read any data needed to to synchronise with disk.
 
2133
        """Re-read any data needed to synchronise with disk.
1310
2134
 
1311
2135
        This method is intended to be called after another repository instance
1312
2136
        (such as one used by a smart server) has inserted data into the
1313
 
        repository. It may not be called during a write group, but may be
1314
 
        called at any other time.
 
2137
        repository. On all repositories this will work outside of write groups.
 
2138
        Some repository formats (pack and newer for breezy native formats)
 
2139
        support refresh_data inside write groups. If called inside a write
 
2140
        group on a repository that does not support refreshing in a write group
 
2141
        IsInWriteGroupError will be raised.
1315
2142
        """
1316
 
        if self.is_in_write_group():
1317
 
            raise errors.InternalBzrError(
1318
 
                "May not refresh_data while in a write group.")
1319
2143
        if self._real_repository is not None:
1320
2144
            self._real_repository.refresh_data()
 
2145
        # Refresh the parents cache for this object
 
2146
        self._unstacked_provider.disable_cache()
 
2147
        self._unstacked_provider.enable_cache()
1321
2148
 
1322
2149
    def revision_ids_to_search_result(self, result_set):
1323
2150
        """Convert a set of revision ids to a graph SearchResult."""
1324
2151
        result_parents = set()
1325
 
        for parents in self.get_graph().get_parent_map(
1326
 
            result_set).itervalues():
 
2152
        for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
1327
2153
            result_parents.update(parents)
1328
2154
        included_keys = result_set.intersection(result_parents)
1329
2155
        start_keys = result_set.difference(included_keys)
1330
2156
        exclude_keys = result_parents.difference(result_set)
1331
 
        result = graph.SearchResult(start_keys, exclude_keys,
 
2157
        result = vf_search.SearchResult(start_keys, exclude_keys,
1332
2158
            len(result_set), result_set)
1333
2159
        return result
1334
2160
 
1335
 
    @needs_read_lock
1336
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
2161
    def search_missing_revision_ids(self, other,
 
2162
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
2163
            limit=None):
1337
2164
        """Return the revision ids that other has that this does not.
1338
2165
 
1339
2166
        These are returned in topological order.
1340
2167
 
1341
2168
        revision_id: only return revision ids included by revision_id.
1342
2169
        """
1343
 
        return repository.InterRepository.get(
1344
 
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
2170
        with self.lock_read():
 
2171
            inter_repo = _mod_repository.InterRepository.get(other, self)
 
2172
            return inter_repo.search_missing_revision_ids(
 
2173
                find_ghosts=find_ghosts, revision_ids=revision_ids,
 
2174
                if_present_ids=if_present_ids, limit=limit)
1345
2175
 
1346
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
2176
    def fetch(self, source, revision_id=None, find_ghosts=False,
1347
2177
            fetch_spec=None):
1348
2178
        # No base implementation to use as RemoteRepository is not a subclass
1349
2179
        # of Repository; so this is a copy of Repository.fetch().
1360
2190
            # check that last_revision is in 'from' and then return a
1361
2191
            # no-operation.
1362
2192
            if (revision_id is not None and
1363
 
                not revision.is_null(revision_id)):
 
2193
                not _mod_revision.is_null(revision_id)):
1364
2194
                self.get_revision(revision_id)
1365
2195
            return 0, []
1366
2196
        # if there is no specific appropriate InterRepository, this will get
1367
2197
        # the InterRepository base class, which raises an
1368
2198
        # IncompatibleRepositories when asked to fetch.
1369
 
        inter = repository.InterRepository.get(source, self)
1370
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
2199
        inter = _mod_repository.InterRepository.get(source, self)
 
2200
        if (fetch_spec is not None and
 
2201
            not getattr(inter, "supports_fetch_spec", False)):
 
2202
            raise errors.UnsupportedOperation(
 
2203
                "fetch_spec not supported for %r" % inter)
 
2204
        return inter.fetch(revision_id=revision_id,
1371
2205
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1372
2206
 
1373
2207
    def create_bundle(self, target, base, fileobj, format=None):
1374
2208
        self._ensure_real()
1375
2209
        self._real_repository.create_bundle(target, base, fileobj, format)
1376
2210
 
1377
 
    @needs_read_lock
1378
 
    def get_ancestry(self, revision_id, topo_sorted=True):
1379
 
        self._ensure_real()
1380
 
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
1381
 
 
1382
2211
    def fileids_altered_by_revision_ids(self, revision_ids):
1383
2212
        self._ensure_real()
1384
2213
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1388
2217
        return self._real_repository._get_versioned_file_checker(
1389
2218
            revisions, revision_versions_cache)
1390
2219
 
 
2220
    def _iter_files_bytes_rpc(self, desired_files, absent):
 
2221
        path = self.controldir._path_for_remote_call(self._client)
 
2222
        lines = []
 
2223
        identifiers = []
 
2224
        for (file_id, revid, identifier) in desired_files:
 
2225
            lines.append(b''.join([
 
2226
                osutils.safe_file_id(file_id),
 
2227
                b'\0',
 
2228
                osutils.safe_revision_id(revid)]))
 
2229
            identifiers.append(identifier)
 
2230
        (response_tuple, response_handler) = (
 
2231
            self._call_with_body_bytes_expecting_body(
 
2232
            b"Repository.iter_files_bytes", (path, ), b"\n".join(lines)))
 
2233
        if response_tuple != (b'ok', ):
 
2234
            response_handler.cancel_read_body()
 
2235
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2236
        byte_stream = response_handler.read_streamed_body()
 
2237
        def decompress_stream(start, byte_stream, unused):
 
2238
            decompressor = zlib.decompressobj()
 
2239
            yield decompressor.decompress(start)
 
2240
            while decompressor.unused_data == b"":
 
2241
                try:
 
2242
                    data = next(byte_stream)
 
2243
                except StopIteration:
 
2244
                    break
 
2245
                yield decompressor.decompress(data)
 
2246
            yield decompressor.flush()
 
2247
            unused.append(decompressor.unused_data)
 
2248
        unused = b""
 
2249
        while True:
 
2250
            while not b"\n" in unused:
 
2251
                try:
 
2252
                    unused += next(byte_stream)
 
2253
                except StopIteration:
 
2254
                    return
 
2255
            header, rest = unused.split(b"\n", 1)
 
2256
            args = header.split(b"\0")
 
2257
            if args[0] == b"absent":
 
2258
                absent[identifiers[int(args[3])]] = (args[1], args[2])
 
2259
                unused = rest
 
2260
                continue
 
2261
            elif args[0] == b"ok":
 
2262
                idx = int(args[1])
 
2263
            else:
 
2264
                raise errors.UnexpectedSmartServerResponse(args)
 
2265
            unused_chunks = []
 
2266
            yield (identifiers[idx],
 
2267
                decompress_stream(rest, byte_stream, unused_chunks))
 
2268
            unused = b"".join(unused_chunks)
 
2269
 
1391
2270
    def iter_files_bytes(self, desired_files):
1392
2271
        """See Repository.iter_file_bytes.
1393
2272
        """
1394
 
        self._ensure_real()
1395
 
        return self._real_repository.iter_files_bytes(desired_files)
 
2273
        try:
 
2274
            absent = {}
 
2275
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
 
2276
                    desired_files, absent):
 
2277
                yield identifier, bytes_iterator
 
2278
            for fallback in self._fallback_repositories:
 
2279
                if not absent:
 
2280
                    break
 
2281
                desired_files = [(key[0], key[1], identifier)
 
2282
                    for identifier, key in viewitems(absent)]
 
2283
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
 
2284
                    del absent[identifier]
 
2285
                    yield identifier, bytes_iterator
 
2286
            if absent:
 
2287
                # There may be more missing items, but raise an exception
 
2288
                # for just one.
 
2289
                missing_identifier = next(iter(absent))
 
2290
                missing_key = absent[missing_identifier]
 
2291
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
 
2292
                    file_id=missing_key[0])
 
2293
        except errors.UnknownSmartMethod:
 
2294
            self._ensure_real()
 
2295
            for (identifier, bytes_iterator) in (
 
2296
                self._real_repository.iter_files_bytes(desired_files)):
 
2297
                yield identifier, bytes_iterator
 
2298
 
 
2299
    def get_cached_parent_map(self, revision_ids):
 
2300
        """See breezy.CachingParentsProvider.get_cached_parent_map"""
 
2301
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
1396
2302
 
1397
2303
    def get_parent_map(self, revision_ids):
1398
 
        """See bzrlib.Graph.get_parent_map()."""
 
2304
        """See breezy.Graph.get_parent_map()."""
1399
2305
        return self._make_parents_provider().get_parent_map(revision_ids)
1400
2306
 
1401
2307
    def _get_parent_map_rpc(self, keys):
1420
2326
            # There is one other "bug" which is that ghosts in
1421
2327
            # get_revision_graph() are not returned at all. But we won't worry
1422
2328
            # about that for now.
1423
 
            for node_id, parent_ids in rg.iteritems():
 
2329
            for node_id, parent_ids in viewitems(rg):
1424
2330
                if parent_ids == ():
1425
2331
                    rg[node_id] = (NULL_REVISION,)
1426
2332
            rg[NULL_REVISION] = ()
1457
2363
        if parents_map is None:
1458
2364
            # Repository is not locked, so there's no cache.
1459
2365
            parents_map = {}
1460
 
        # start_set is all the keys in the cache
1461
 
        start_set = set(parents_map)
1462
 
        # result set is all the references to keys in the cache
1463
 
        result_parents = set()
1464
 
        for parents in parents_map.itervalues():
1465
 
            result_parents.update(parents)
1466
 
        stop_keys = result_parents.difference(start_set)
1467
 
        # We don't need to send ghosts back to the server as a position to
1468
 
        # stop either.
1469
 
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
1470
 
        key_count = len(parents_map)
1471
 
        if (NULL_REVISION in result_parents
1472
 
            and NULL_REVISION in self._unstacked_provider.missing_keys):
1473
 
            # If we pruned NULL_REVISION from the stop_keys because it's also
1474
 
            # in our cache of "missing" keys we need to increment our key count
1475
 
            # by 1, because the reconsitituted SearchResult on the server will
1476
 
            # still consider NULL_REVISION to be an included key.
1477
 
            key_count += 1
1478
 
        included_keys = start_set.intersection(result_parents)
1479
 
        start_set.difference_update(included_keys)
 
2366
        if _DEFAULT_SEARCH_DEPTH <= 0:
 
2367
            (start_set, stop_keys,
 
2368
             key_count) = vf_search.search_result_from_parent_map(
 
2369
                parents_map, self._unstacked_provider.missing_keys)
 
2370
        else:
 
2371
            (start_set, stop_keys,
 
2372
             key_count) = vf_search.limited_search_result_from_parent_map(
 
2373
                parents_map, self._unstacked_provider.missing_keys,
 
2374
                keys, depth=_DEFAULT_SEARCH_DEPTH)
1480
2375
        recipe = ('manual', start_set, stop_keys, key_count)
1481
2376
        body = self._serialise_search_recipe(recipe)
1482
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2377
        path = self.controldir._path_for_remote_call(self._client)
1483
2378
        for key in keys:
1484
 
            if type(key) is not str:
 
2379
            if not isinstance(key, bytes):
1485
2380
                raise ValueError(
1486
 
                    "key %r not a plain string" % (key,))
1487
 
        verb = 'Repository.get_parent_map'
1488
 
        args = (path, 'include-missing:') + tuple(keys)
 
2381
                    "key %r not a bytes string" % (key,))
 
2382
        verb = b'Repository.get_parent_map'
 
2383
        args = (path, b'include-missing:') + tuple(keys)
1489
2384
        try:
1490
2385
            response = self._call_with_body_bytes_expecting_body(
1491
2386
                verb, args, body)
1504
2399
            # Recurse just once and we should use the fallback code.
1505
2400
            return self._get_parent_map_rpc(keys)
1506
2401
        response_tuple, response_handler = response
1507
 
        if response_tuple[0] not in ['ok']:
 
2402
        if response_tuple[0] not in [b'ok']:
1508
2403
            response_handler.cancel_read_body()
1509
2404
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1510
 
        if response_tuple[0] == 'ok':
 
2405
        if response_tuple[0] == b'ok':
1511
2406
            coded = bz2.decompress(response_handler.read_body_bytes())
1512
 
            if coded == '':
 
2407
            if coded == b'':
1513
2408
                # no revisions found
1514
2409
                return {}
1515
 
            lines = coded.split('\n')
 
2410
            lines = coded.split(b'\n')
1516
2411
            revision_graph = {}
1517
2412
            for line in lines:
1518
2413
                d = tuple(line.split())
1520
2415
                    revision_graph[d[0]] = d[1:]
1521
2416
                else:
1522
2417
                    # No parents:
1523
 
                    if d[0].startswith('missing:'):
 
2418
                    if d[0].startswith(b'missing:'):
1524
2419
                        revid = d[0][8:]
1525
2420
                        self._unstacked_provider.note_missing_key(revid)
1526
2421
                    else:
1529
2424
                        revision_graph[d[0]] = (NULL_REVISION,)
1530
2425
            return revision_graph
1531
2426
 
1532
 
    @needs_read_lock
1533
2427
    def get_signature_text(self, revision_id):
1534
 
        self._ensure_real()
1535
 
        return self._real_repository.get_signature_text(revision_id)
 
2428
        with self.lock_read():
 
2429
            path = self.controldir._path_for_remote_call(self._client)
 
2430
            try:
 
2431
                response_tuple, response_handler = self._call_expecting_body(
 
2432
                    b'Repository.get_revision_signature_text', path, revision_id)
 
2433
            except errors.UnknownSmartMethod:
 
2434
                self._ensure_real()
 
2435
                return self._real_repository.get_signature_text(revision_id)
 
2436
            except errors.NoSuchRevision as err:
 
2437
                for fallback in self._fallback_repositories:
 
2438
                    try:
 
2439
                        return fallback.get_signature_text(revision_id)
 
2440
                    except errors.NoSuchRevision:
 
2441
                        pass
 
2442
                raise err
 
2443
            else:
 
2444
                if response_tuple[0] != b'ok':
 
2445
                    raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2446
                return response_handler.read_body_bytes()
1536
2447
 
1537
 
    @needs_read_lock
1538
2448
    def _get_inventory_xml(self, revision_id):
1539
 
        self._ensure_real()
1540
 
        return self._real_repository._get_inventory_xml(revision_id)
 
2449
        with self.lock_read():
 
2450
            # This call is used by older working tree formats,
 
2451
            # which stored a serialized basis inventory.
 
2452
            self._ensure_real()
 
2453
            return self._real_repository._get_inventory_xml(revision_id)
1541
2454
 
1542
2455
    def reconcile(self, other=None, thorough=False):
1543
 
        self._ensure_real()
1544
 
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
2456
        from ..reconcile import RepoReconciler
 
2457
        with self.lock_write():
 
2458
            path = self.controldir._path_for_remote_call(self._client)
 
2459
            try:
 
2460
                response, handler = self._call_expecting_body(
 
2461
                    b'Repository.reconcile', path, self._lock_token)
 
2462
            except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
 
2463
                self._ensure_real()
 
2464
                return self._real_repository.reconcile(other=other, thorough=thorough)
 
2465
            if response != (b'ok', ):
 
2466
                raise errors.UnexpectedSmartServerResponse(response)
 
2467
            body = handler.read_body_bytes()
 
2468
            result = RepoReconciler(self)
 
2469
            for line in body.split(b'\n'):
 
2470
                if not line:
 
2471
                    continue
 
2472
                key, val_text = line.split(b':')
 
2473
                if key == b"garbage_inventories":
 
2474
                    result.garbage_inventories = int(val_text)
 
2475
                elif key == b"inconsistent_parents":
 
2476
                    result.inconsistent_parents = int(val_text)
 
2477
                else:
 
2478
                    mutter("unknown reconcile key %r" % key)
 
2479
            return result
1545
2480
 
1546
2481
    def all_revision_ids(self):
1547
 
        self._ensure_real()
1548
 
        return self._real_repository.all_revision_ids()
1549
 
 
1550
 
    @needs_read_lock
 
2482
        path = self.controldir._path_for_remote_call(self._client)
 
2483
        try:
 
2484
            response_tuple, response_handler = self._call_expecting_body(
 
2485
                b"Repository.all_revision_ids", path)
 
2486
        except errors.UnknownSmartMethod:
 
2487
            self._ensure_real()
 
2488
            return self._real_repository.all_revision_ids()
 
2489
        if response_tuple != (b"ok", ):
 
2490
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2491
        revids = set(response_handler.read_body_bytes().splitlines())
 
2492
        for fallback in self._fallback_repositories:
 
2493
            revids.update(set(fallback.all_revision_ids()))
 
2494
        return list(revids)
 
2495
 
 
2496
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2497
        """Return Tree for a revision on this branch with only some files.
 
2498
 
 
2499
        :param revision_ids: a sequence of revision-ids;
 
2500
          a revision-id may not be None or b'null:'
 
2501
        :param file_ids: if not None, the result is filtered
 
2502
          so that only those file-ids, their parents and their
 
2503
          children are included.
 
2504
        """
 
2505
        inventories = self.iter_inventories(revision_ids)
 
2506
        for inv in inventories:
 
2507
            # Should we introduce a FilteredRevisionTree class rather
 
2508
            # than pre-filter the inventory here?
 
2509
            filtered_inv = inv.filter(file_ids)
 
2510
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2511
 
1551
2512
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1552
 
        self._ensure_real()
1553
 
        return self._real_repository.get_deltas_for_revisions(revisions,
1554
 
            specific_fileids=specific_fileids)
1555
 
 
1556
 
    @needs_read_lock
 
2513
        with self.lock_read():
 
2514
            medium = self._client._medium
 
2515
            if medium._is_remote_before((1, 2)):
 
2516
                self._ensure_real()
 
2517
                for delta in self._real_repository.get_deltas_for_revisions(
 
2518
                        revisions, specific_fileids):
 
2519
                    yield delta
 
2520
                return
 
2521
            # Get the revision-ids of interest
 
2522
            required_trees = set()
 
2523
            for revision in revisions:
 
2524
                required_trees.add(revision.revision_id)
 
2525
                required_trees.update(revision.parent_ids[:1])
 
2526
 
 
2527
            # Get the matching filtered trees. Note that it's more
 
2528
            # efficient to pass filtered trees to changes_from() rather
 
2529
            # than doing the filtering afterwards. changes_from() could
 
2530
            # arguably do the filtering itself but it's path-based, not
 
2531
            # file-id based, so filtering before or afterwards is
 
2532
            # currently easier.
 
2533
            if specific_fileids is None:
 
2534
                trees = dict((t.get_revision_id(), t) for
 
2535
                    t in self.revision_trees(required_trees))
 
2536
            else:
 
2537
                trees = dict((t.get_revision_id(), t) for
 
2538
                    t in self._filtered_revision_trees(required_trees,
 
2539
                    specific_fileids))
 
2540
 
 
2541
            # Calculate the deltas
 
2542
            for revision in revisions:
 
2543
                if not revision.parent_ids:
 
2544
                    old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
2545
                else:
 
2546
                    old_tree = trees[revision.parent_ids[0]]
 
2547
                yield trees[revision.revision_id].changes_from(old_tree)
 
2548
 
1557
2549
    def get_revision_delta(self, revision_id, specific_fileids=None):
1558
 
        self._ensure_real()
1559
 
        return self._real_repository.get_revision_delta(revision_id,
1560
 
            specific_fileids=specific_fileids)
 
2550
        with self.lock_read():
 
2551
            r = self.get_revision(revision_id)
 
2552
            return list(self.get_deltas_for_revisions([r],
 
2553
                specific_fileids=specific_fileids))[0]
1561
2554
 
1562
 
    @needs_read_lock
1563
2555
    def revision_trees(self, revision_ids):
1564
 
        self._ensure_real()
1565
 
        return self._real_repository.revision_trees(revision_ids)
 
2556
        with self.lock_read():
 
2557
            inventories = self.iter_inventories(revision_ids)
 
2558
            for inv in inventories:
 
2559
                yield RemoteInventoryTree(self, inv, inv.revision_id)
1566
2560
 
1567
 
    @needs_read_lock
1568
2561
    def get_revision_reconcile(self, revision_id):
1569
 
        self._ensure_real()
1570
 
        return self._real_repository.get_revision_reconcile(revision_id)
 
2562
        with self.lock_read():
 
2563
            self._ensure_real()
 
2564
            return self._real_repository.get_revision_reconcile(revision_id)
1571
2565
 
1572
 
    @needs_read_lock
1573
2566
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1574
 
        self._ensure_real()
1575
 
        return self._real_repository.check(revision_ids=revision_ids,
1576
 
            callback_refs=callback_refs, check_repo=check_repo)
 
2567
        with self.lock_read():
 
2568
            self._ensure_real()
 
2569
            return self._real_repository.check(revision_ids=revision_ids,
 
2570
                callback_refs=callback_refs, check_repo=check_repo)
1577
2571
 
1578
2572
    def copy_content_into(self, destination, revision_id=None):
1579
 
        self._ensure_real()
1580
 
        return self._real_repository.copy_content_into(
1581
 
            destination, revision_id=revision_id)
 
2573
        """Make a complete copy of the content in self into destination.
 
2574
 
 
2575
        This is a destructive operation! Do not use it on existing
 
2576
        repositories.
 
2577
        """
 
2578
        interrepo = _mod_repository.InterRepository.get(self, destination)
 
2579
        return interrepo.copy_content(revision_id)
1582
2580
 
1583
2581
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1584
2582
        # get a tarball of the remote repository, and copy from that into the
1585
2583
        # destination
1586
 
        from bzrlib import osutils
1587
2584
        import tarfile
1588
2585
        # TODO: Maybe a progress bar while streaming the tarball?
1589
 
        note("Copying repository content as tarball...")
 
2586
        note(gettext("Copying repository content as tarball..."))
1590
2587
        tar_file = self._get_tarball('bz2')
1591
2588
        if tar_file is None:
1592
2589
            return None
1596
2593
                mode='r|bz2')
1597
2594
            tmpdir = osutils.mkdtemp()
1598
2595
            try:
1599
 
                _extract_tar(tar, tmpdir)
1600
 
                tmp_bzrdir = BzrDir.open(tmpdir)
 
2596
                tar.extractall(tmpdir)
 
2597
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1601
2598
                tmp_repo = tmp_bzrdir.open_repository()
1602
2599
                tmp_repo.copy_content_into(destination, revision_id)
1603
2600
            finally:
1618
2615
        self._ensure_real()
1619
2616
        return self._real_repository.inventories
1620
2617
 
1621
 
    @needs_write_lock
1622
2618
    def pack(self, hint=None, clean_obsolete_packs=False):
1623
2619
        """Compress the data within the repository.
1624
 
 
1625
 
        This is not currently implemented within the smart server.
1626
2620
        """
1627
 
        self._ensure_real()
1628
 
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
2621
        if hint is None:
 
2622
            body = b""
 
2623
        else:
 
2624
            body = b"".join([l+b"\n" for l in hint])
 
2625
        with self.lock_write():
 
2626
            path = self.controldir._path_for_remote_call(self._client)
 
2627
            try:
 
2628
                response, handler = self._call_with_body_bytes_expecting_body(
 
2629
                    b'Repository.pack', (path, self._lock_token,
 
2630
                        str(clean_obsolete_packs).encode('ascii')), body)
 
2631
            except errors.UnknownSmartMethod:
 
2632
                self._ensure_real()
 
2633
                return self._real_repository.pack(hint=hint,
 
2634
                    clean_obsolete_packs=clean_obsolete_packs)
 
2635
            handler.cancel_read_body()
 
2636
            if response != (b'ok', ):
 
2637
                raise errors.UnexpectedSmartServerResponse(response)
1629
2638
 
1630
2639
    @property
1631
2640
    def revisions(self):
1632
2641
        """Decorate the real repository for now.
1633
2642
 
1634
 
        In the short term this should become a real object to intercept graph
1635
 
        lookups.
1636
 
 
1637
2643
        In the long term a full blown network facility is needed.
1638
2644
        """
1639
2645
        self._ensure_real()
1641
2647
 
1642
2648
    def set_make_working_trees(self, new_value):
1643
2649
        if new_value:
1644
 
            new_value_str = "True"
 
2650
            new_value_str = b"True"
1645
2651
        else:
1646
 
            new_value_str = "False"
1647
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2652
            new_value_str = b"False"
 
2653
        path = self.controldir._path_for_remote_call(self._client)
1648
2654
        try:
1649
2655
            response = self._call(
1650
 
                'Repository.set_make_working_trees', path, new_value_str)
 
2656
                b'Repository.set_make_working_trees', path, new_value_str)
1651
2657
        except errors.UnknownSmartMethod:
1652
2658
            self._ensure_real()
1653
2659
            self._real_repository.set_make_working_trees(new_value)
1654
2660
        else:
1655
 
            if response[0] != 'ok':
 
2661
            if response[0] != b'ok':
1656
2662
                raise errors.UnexpectedSmartServerResponse(response)
1657
2663
 
1658
2664
    @property
1665
2671
        self._ensure_real()
1666
2672
        return self._real_repository.signatures
1667
2673
 
1668
 
    @needs_write_lock
1669
2674
    def sign_revision(self, revision_id, gpg_strategy):
1670
 
        self._ensure_real()
1671
 
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
2675
        with self.lock_write():
 
2676
            testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2677
            plaintext = testament.as_short_text()
 
2678
            self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1672
2679
 
1673
2680
    @property
1674
2681
    def texts(self):
1680
2687
        self._ensure_real()
1681
2688
        return self._real_repository.texts
1682
2689
 
1683
 
    @needs_read_lock
1684
 
    def get_revisions(self, revision_ids):
1685
 
        self._ensure_real()
1686
 
        return self._real_repository.get_revisions(revision_ids)
 
2690
    def _iter_revisions_rpc(self, revision_ids):
 
2691
        body = b"\n".join(revision_ids)
 
2692
        path = self.controldir._path_for_remote_call(self._client)
 
2693
        response_tuple, response_handler = (
 
2694
            self._call_with_body_bytes_expecting_body(
 
2695
            b"Repository.iter_revisions", (path, ), body))
 
2696
        if response_tuple[0] != b"ok":
 
2697
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2698
        serializer_format = response_tuple[1].decode('ascii')
 
2699
        serializer = serializer_format_registry.get(serializer_format)
 
2700
        byte_stream = response_handler.read_streamed_body()
 
2701
        decompressor = zlib.decompressobj()
 
2702
        chunks = []
 
2703
        for bytes in byte_stream:
 
2704
            chunks.append(decompressor.decompress(bytes))
 
2705
            if decompressor.unused_data != b"":
 
2706
                chunks.append(decompressor.flush())
 
2707
                yield serializer.read_revision_from_string(b"".join(chunks))
 
2708
                unused = decompressor.unused_data
 
2709
                decompressor = zlib.decompressobj()
 
2710
                chunks = [decompressor.decompress(unused)]
 
2711
        chunks.append(decompressor.flush())
 
2712
        text = b"".join(chunks)
 
2713
        if text != b"":
 
2714
            yield serializer.read_revision_from_string(b"".join(chunks))
 
2715
 
 
2716
    def iter_revisions(self, revision_ids):
 
2717
        for rev_id in revision_ids:
 
2718
            if not rev_id or not isinstance(rev_id, bytes):
 
2719
                raise errors.InvalidRevisionId(
 
2720
                    revision_id=rev_id, branch=self)
 
2721
        with self.lock_read():
 
2722
            try:
 
2723
                missing = set(revision_ids)
 
2724
                for rev in self._iter_revisions_rpc(revision_ids):
 
2725
                    missing.remove(rev.revision_id)
 
2726
                    yield (rev.revision_id, rev)
 
2727
                for fallback in self._fallback_repositories:
 
2728
                    if not missing:
 
2729
                        break
 
2730
                    for (revid, rev) in fallback.iter_revisions(missing):
 
2731
                        if rev is not None:
 
2732
                            yield (revid, rev)
 
2733
                            missing.remove(revid)
 
2734
                for revid in missing:
 
2735
                    yield (revid, None)
 
2736
            except errors.UnknownSmartMethod:
 
2737
                self._ensure_real()
 
2738
                for entry in self._real_repository.iter_revisions(revision_ids):
 
2739
                    yield entry
1687
2740
 
1688
2741
    def supports_rich_root(self):
1689
2742
        return self._format.rich_root_data
1690
2743
 
1691
 
    def iter_reverse_revision_history(self, revision_id):
1692
 
        self._ensure_real()
1693
 
        return self._real_repository.iter_reverse_revision_history(revision_id)
1694
 
 
1695
2744
    @property
1696
2745
    def _serializer(self):
1697
2746
        return self._format._serializer
1698
2747
 
1699
2748
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1700
 
        self._ensure_real()
1701
 
        return self._real_repository.store_revision_signature(
1702
 
            gpg_strategy, plaintext, revision_id)
 
2749
        with self.lock_write():
 
2750
            signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
 
2751
            self.add_signature_text(revision_id, signature)
1703
2752
 
1704
2753
    def add_signature_text(self, revision_id, signature):
1705
 
        self._ensure_real()
1706
 
        return self._real_repository.add_signature_text(revision_id, signature)
 
2754
        if self._real_repository:
 
2755
            # If there is a real repository the write group will
 
2756
            # be in the real repository as well, so use that:
 
2757
            self._ensure_real()
 
2758
            return self._real_repository.add_signature_text(
 
2759
                revision_id, signature)
 
2760
        path = self.controldir._path_for_remote_call(self._client)
 
2761
        response, handler = self._call_with_body_bytes_expecting_body(
 
2762
            b'Repository.add_signature_text', (path, self._lock_token,
 
2763
                revision_id) +
 
2764
            tuple([token.encode('utf-8') for token in self._write_group_tokens]),
 
2765
            signature)
 
2766
        handler.cancel_read_body()
 
2767
        self.refresh_data()
 
2768
        if response[0] != b'ok':
 
2769
            raise errors.UnexpectedSmartServerResponse(response)
 
2770
        self._write_group_tokens = [token.decode('utf-8') for token in response[1:]]
1707
2771
 
1708
2772
    def has_signature_for_revision_id(self, revision_id):
1709
 
        self._ensure_real()
1710
 
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
2773
        path = self.controldir._path_for_remote_call(self._client)
 
2774
        try:
 
2775
            response = self._call(b'Repository.has_signature_for_revision_id',
 
2776
                path, revision_id)
 
2777
        except errors.UnknownSmartMethod:
 
2778
            self._ensure_real()
 
2779
            return self._real_repository.has_signature_for_revision_id(
 
2780
                revision_id)
 
2781
        if response[0] not in (b'yes', b'no'):
 
2782
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2783
        if response[0] == b'yes':
 
2784
            return True
 
2785
        for fallback in self._fallback_repositories:
 
2786
            if fallback.has_signature_for_revision_id(revision_id):
 
2787
                return True
 
2788
        return False
 
2789
 
 
2790
    def verify_revision_signature(self, revision_id, gpg_strategy):
 
2791
        with self.lock_read():
 
2792
            if not self.has_signature_for_revision_id(revision_id):
 
2793
                return gpg.SIGNATURE_NOT_SIGNED, None
 
2794
            signature = self.get_signature_text(revision_id)
 
2795
 
 
2796
            testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2797
 
 
2798
            (status, key, signed_plaintext) = gpg_strategy.verify(signature)
 
2799
            if testament.as_short_text() != signed_plaintext:
 
2800
                return gpg.SIGNATURE_NOT_VALID, None
 
2801
            return (status, key)
1711
2802
 
1712
2803
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1713
2804
        self._ensure_real()
1714
2805
        return self._real_repository.item_keys_introduced_by(revision_ids,
1715
2806
            _files_pb=_files_pb)
1716
2807
 
1717
 
    def revision_graph_can_have_wrong_parents(self):
1718
 
        # The answer depends on the remote repo format.
1719
 
        self._ensure_real()
1720
 
        return self._real_repository.revision_graph_can_have_wrong_parents()
1721
 
 
1722
2808
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1723
2809
        self._ensure_real()
1724
2810
        return self._real_repository._find_inconsistent_revision_parents(
1732
2818
        providers = [self._unstacked_provider]
1733
2819
        if other is not None:
1734
2820
            providers.insert(0, other)
1735
 
        providers.extend(r._make_parents_provider() for r in
1736
 
                         self._fallback_repositories)
1737
 
        return graph.StackedParentsProvider(providers)
 
2821
        return graph.StackedParentsProvider(_LazyListJoin(
 
2822
            providers, self._fallback_repositories))
1738
2823
 
1739
2824
    def _serialise_search_recipe(self, recipe):
1740
2825
        """Serialise a graph search recipe.
1742
2827
        :param recipe: A search recipe (start, stop, count).
1743
2828
        :return: Serialised bytes.
1744
2829
        """
1745
 
        start_keys = ' '.join(recipe[1])
1746
 
        stop_keys = ' '.join(recipe[2])
1747
 
        count = str(recipe[3])
1748
 
        return '\n'.join((start_keys, stop_keys, count))
 
2830
        start_keys = b' '.join(recipe[1])
 
2831
        stop_keys = b' '.join(recipe[2])
 
2832
        count = str(recipe[3]).encode('ascii')
 
2833
        return b'\n'.join((start_keys, stop_keys, count))
1749
2834
 
1750
2835
    def _serialise_search_result(self, search_result):
1751
 
        if isinstance(search_result, graph.PendingAncestryResult):
1752
 
            parts = ['ancestry-of']
1753
 
            parts.extend(search_result.heads)
1754
 
        else:
1755
 
            recipe = search_result.get_recipe()
1756
 
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
1757
 
        return '\n'.join(parts)
 
2836
        parts = search_result.get_network_struct()
 
2837
        return b'\n'.join(parts)
1758
2838
 
1759
2839
    def autopack(self):
1760
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2840
        path = self.controldir._path_for_remote_call(self._client)
1761
2841
        try:
1762
 
            response = self._call('PackRepository.autopack', path)
 
2842
            response = self._call(b'PackRepository.autopack', path)
1763
2843
        except errors.UnknownSmartMethod:
1764
2844
            self._ensure_real()
1765
2845
            self._real_repository._pack_collection.autopack()
1766
2846
            return
1767
2847
        self.refresh_data()
1768
 
        if response[0] != 'ok':
1769
 
            raise errors.UnexpectedSmartServerResponse(response)
1770
 
 
1771
 
 
1772
 
class RemoteStreamSink(repository.StreamSink):
 
2848
        if response[0] != b'ok':
 
2849
            raise errors.UnexpectedSmartServerResponse(response)
 
2850
 
 
2851
    def _revision_archive(self, revision_id, format, name, root, subdir,
 
2852
                          force_mtime=None):
 
2853
        path = self.controldir._path_for_remote_call(self._client)
 
2854
        format = format or ''
 
2855
        root = root or ''
 
2856
        subdir = subdir or ''
 
2857
        force_mtime = int(force_mtime) if force_mtime is not None else None
 
2858
        try:
 
2859
            response, protocol = self._call_expecting_body(
 
2860
                b'Repository.revision_archive', path,
 
2861
                revision_id,
 
2862
                format.encode('ascii'),
 
2863
                os.path.basename(name).encode('utf-8'),
 
2864
                root.encode('utf-8'),
 
2865
                subdir.encode('utf-8'),
 
2866
                force_mtime)
 
2867
        except errors.UnknownSmartMethod:
 
2868
            return None
 
2869
        if response[0] == b'ok':
 
2870
            return iter([protocol.read_body_bytes()])
 
2871
        raise errors.UnexpectedSmartServerResponse(response)
 
2872
 
 
2873
    def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
 
2874
        path = self.controldir._path_for_remote_call(self._client)
 
2875
        tree_path = tree_path.encode('utf-8')
 
2876
        file_id = file_id or b''
 
2877
        default_revision = default_revision or b''
 
2878
        try:
 
2879
            response, handler = self._call_expecting_body(
 
2880
                b'Repository.annotate_file_revision', path,
 
2881
                revid, tree_path, file_id, default_revision)
 
2882
        except errors.UnknownSmartMethod:
 
2883
            return None
 
2884
        if response[0] != b'ok':
 
2885
            raise errors.UnexpectedSmartServerResponse(response)
 
2886
        return map(tuple, bencode.bdecode(handler.read_body_bytes()))
 
2887
 
 
2888
 
 
2889
class RemoteStreamSink(vf_repository.StreamSink):
1773
2890
 
1774
2891
    def _insert_real(self, stream, src_format, resume_tokens):
1775
2892
        self.target_repo._ensure_real()
1779
2896
            self.target_repo.autopack()
1780
2897
        return result
1781
2898
 
 
2899
    def insert_missing_keys(self, source, missing_keys):
 
2900
        if (isinstance(source, RemoteStreamSource) and
 
2901
                source.from_repository._client._medium == self.target_repo._client._medium):
 
2902
            # Streaming from and to the same medium is tricky, since we don't support
 
2903
            # more than one concurrent request. For now, just force VFS.
 
2904
            stream = source._get_real_stream_for_missing_keys(missing_keys)
 
2905
        else:
 
2906
            stream = source.get_stream_for_missing_keys(missing_keys)
 
2907
        return self.insert_stream_without_locking(stream,
 
2908
            self.target_repo._format)
 
2909
 
1782
2910
    def insert_stream(self, stream, src_format, resume_tokens):
1783
2911
        target = self.target_repo
1784
2912
        target._unstacked_provider.missing_keys.clear()
1785
 
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
 
2913
        candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
1786
2914
        if target._lock_token:
1787
 
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1788
 
            lock_args = (target._lock_token or '',)
 
2915
            candidate_calls.append((b'Repository.insert_stream_locked', (1, 14)))
 
2916
            lock_args = (target._lock_token or b'',)
1789
2917
        else:
1790
 
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
2918
            candidate_calls.append((b'Repository.insert_stream', (1, 13)))
1791
2919
            lock_args = ()
1792
2920
        client = target._client
1793
2921
        medium = client._medium
1794
 
        path = target.bzrdir._path_for_remote_call(client)
 
2922
        path = target.controldir._path_for_remote_call(client)
1795
2923
        # Probe for the verb to use with an empty stream before sending the
1796
2924
        # real stream to it.  We do this both to avoid the risk of sending a
1797
2925
        # large request that is then rejected, and because we don't want to
1808
2936
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1809
2937
            try:
1810
2938
                response = client.call_with_body_stream(
1811
 
                    (verb, path, '') + lock_args, byte_stream)
 
2939
                    (verb, path, b'') + lock_args, byte_stream)
1812
2940
            except errors.UnknownSmartMethod:
1813
2941
                medium._remember_remote_is_before(required_version)
1814
2942
            else:
1827
2955
            stream = self._stop_stream_if_inventory_delta(stream)
1828
2956
        byte_stream = smart_repo._stream_to_byte_stream(
1829
2957
            stream, src_format)
1830
 
        resume_tokens = ' '.join(resume_tokens)
 
2958
        resume_tokens = b' '.join(resume_tokens)
1831
2959
        response = client.call_with_body_stream(
1832
2960
            (verb, path, resume_tokens) + lock_args, byte_stream)
1833
 
        if response[0][0] not in ('ok', 'missing-basis'):
 
2961
        if response[0][0] not in (b'ok', b'missing-basis'):
1834
2962
            raise errors.UnexpectedSmartServerResponse(response)
1835
2963
        if self._last_substream is not None:
1836
2964
            # The stream included an inventory-delta record, but the remote
1838
2966
            # rest of the stream via VFS.
1839
2967
            self.target_repo.refresh_data()
1840
2968
            return self._resume_stream_with_vfs(response, src_format)
1841
 
        if response[0][0] == 'missing-basis':
 
2969
        if response[0][0] == b'missing-basis':
1842
2970
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
2971
            resume_tokens = tokens
1844
 
            return resume_tokens, set(missing_keys)
 
2972
            return resume_tokens, set((entry[0].decode('utf-8'), ) + entry[1:] for entry in missing_keys)
1845
2973
        else:
1846
2974
            self.target_repo.refresh_data()
1847
2975
            return [], set()
1850
2978
        """Resume sending a stream via VFS, first resending the record and
1851
2979
        substream that couldn't be sent via an insert_stream verb.
1852
2980
        """
1853
 
        if response[0][0] == 'missing-basis':
 
2981
        if response[0][0] == b'missing-basis':
1854
2982
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1855
2983
            # Ignore missing_keys, we haven't finished inserting yet
1856
2984
        else:
1876
3004
        self._last_substream and self._last_stream so that the stream can be
1877
3005
        resumed by _resume_stream_with_vfs.
1878
3006
        """
1879
 
                    
 
3007
 
1880
3008
        stream_iter = iter(stream)
1881
3009
        for substream_kind, substream in stream_iter:
1882
3010
            if substream_kind == 'inventory-deltas':
1885
3013
                return
1886
3014
            else:
1887
3015
                yield substream_kind, substream
1888
 
            
1889
 
 
1890
 
class RemoteStreamSource(repository.StreamSource):
 
3016
 
 
3017
 
 
3018
class RemoteStreamSource(vf_repository.StreamSource):
1891
3019
    """Stream data from a remote server."""
1892
3020
 
1893
3021
    def get_stream(self, search):
1906
3034
            sources.append(repo)
1907
3035
        return self.missing_parents_chain(search, sources)
1908
3036
 
1909
 
    def get_stream_for_missing_keys(self, missing_keys):
 
3037
    def _get_real_stream_for_missing_keys(self, missing_keys):
1910
3038
        self.from_repository._ensure_real()
1911
3039
        real_repo = self.from_repository._real_repository
1912
3040
        real_source = real_repo._get_source(self.to_format)
1913
3041
        return real_source.get_stream_for_missing_keys(missing_keys)
1914
3042
 
 
3043
    def get_stream_for_missing_keys(self, missing_keys):
 
3044
        if not isinstance(self.from_repository, RemoteRepository):
 
3045
            return self._get_real_stream_for_missing_keys(missing_keys)
 
3046
        client = self.from_repository._client
 
3047
        medium = client._medium
 
3048
        if medium._is_remote_before((3, 0)):
 
3049
            return self._get_real_stream_for_missing_keys(missing_keys)
 
3050
        path = self.from_repository.controldir._path_for_remote_call(client)
 
3051
        args = (path, self.to_format.network_name())
 
3052
        search_bytes = b'\n'.join(
 
3053
            [b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
 
3054
        try:
 
3055
            response, handler = self.from_repository._call_with_body_bytes_expecting_body(
 
3056
                b'Repository.get_stream_for_missing_keys', args, search_bytes)
 
3057
        except (errors.UnknownSmartMethod, errors.UnknownFormatError):
 
3058
            return self._get_real_stream_for_missing_keys(missing_keys)
 
3059
        if response[0] != b'ok':
 
3060
            raise errors.UnexpectedSmartServerResponse(response)
 
3061
        byte_stream = handler.read_streamed_body()
 
3062
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
3063
            self._record_counter)
 
3064
        if src_format.network_name() != self.from_repository._format.network_name():
 
3065
            raise AssertionError(
 
3066
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
3067
                src_format.network_name(), repo._format.network_name()))
 
3068
        return stream
 
3069
 
1915
3070
    def _real_stream(self, repo, search):
1916
3071
        """Get a stream for search from repo.
1917
 
        
1918
 
        This never called RemoteStreamSource.get_stream, and is a heler
1919
 
        for RemoteStreamSource._get_stream to allow getting a stream 
 
3072
 
 
3073
        This never called RemoteStreamSource.get_stream, and is a helper
 
3074
        for RemoteStreamSource._get_stream to allow getting a stream
1920
3075
        reliably whether fallback back because of old servers or trying
1921
3076
        to stream from a non-RemoteRepository (which the stacked support
1922
3077
        code will do).
1947
3102
            return self._real_stream(repo, search)
1948
3103
        client = repo._client
1949
3104
        medium = client._medium
1950
 
        path = repo.bzrdir._path_for_remote_call(client)
 
3105
        path = repo.controldir._path_for_remote_call(client)
1951
3106
        search_bytes = repo._serialise_search_result(search)
1952
3107
        args = (path, self.to_format.network_name())
1953
3108
        candidate_verbs = [
1954
 
            ('Repository.get_stream_1.19', (1, 19)),
1955
 
            ('Repository.get_stream', (1, 13))]
 
3109
            (b'Repository.get_stream_1.19', (1, 19)),
 
3110
            (b'Repository.get_stream', (1, 13))]
 
3111
 
1956
3112
        found_verb = False
1957
3113
        for verb, version in candidate_verbs:
1958
3114
            if medium._is_remote_before(version):
1962
3118
                    verb, args, search_bytes)
1963
3119
            except errors.UnknownSmartMethod:
1964
3120
                medium._remember_remote_is_before(version)
 
3121
            except errors.UnknownErrorFromSmartServer as e:
 
3122
                if isinstance(search, vf_search.EverythingResult):
 
3123
                    error_verb = e.error_from_smart_server.error_verb
 
3124
                    if error_verb == b'BadSearch':
 
3125
                        # Pre-2.4 servers don't support this sort of search.
 
3126
                        # XXX: perhaps falling back to VFS on BadSearch is a
 
3127
                        # good idea in general?  It might provide a little bit
 
3128
                        # of protection against client-side bugs.
 
3129
                        medium._remember_remote_is_before((2, 4))
 
3130
                        break
 
3131
                raise
1965
3132
            else:
1966
3133
                response_tuple, response_handler = response
1967
3134
                found_verb = True
1968
3135
                break
1969
3136
        if not found_verb:
1970
3137
            return self._real_stream(repo, search)
1971
 
        if response_tuple[0] != 'ok':
 
3138
        if response_tuple[0] != b'ok':
1972
3139
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
3140
        byte_stream = response_handler.read_streamed_body()
1974
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
3141
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
3142
            self._record_counter)
1975
3143
        if src_format.network_name() != repo._format.network_name():
1976
3144
            raise AssertionError(
1977
3145
                "Mismatched RemoteRepository and stream src %r, %r" % (
2021
3189
    """
2022
3190
 
2023
3191
    def __init__(self, bzrdir, _client):
2024
 
        self.bzrdir = bzrdir
 
3192
        self.controldir = bzrdir
2025
3193
        self._client = _client
2026
3194
        self._need_find_modes = True
2027
3195
        LockableFiles.__init__(
2038
3206
 
2039
3207
    def __init__(self, network_name=None):
2040
3208
        super(RemoteBranchFormat, self).__init__()
2041
 
        self._matchingbzrdir = RemoteBzrDirFormat()
2042
 
        self._matchingbzrdir.set_branch_format(self)
 
3209
        self._matchingcontroldir = RemoteBzrDirFormat()
 
3210
        self._matchingcontroldir.set_branch_format(self)
2043
3211
        self._custom_format = None
2044
3212
        self._network_name = network_name
2045
3213
 
2049
3217
 
2050
3218
    def _ensure_real(self):
2051
3219
        if self._custom_format is None:
2052
 
            self._custom_format = branch.network_format_registry.get(
2053
 
                self._network_name)
 
3220
            try:
 
3221
                self._custom_format = branch.network_format_registry.get(
 
3222
                    self._network_name)
 
3223
            except KeyError:
 
3224
                raise errors.UnknownFormatError(kind='branch',
 
3225
                    format=self._network_name)
2054
3226
 
2055
3227
    def get_format_description(self):
2056
3228
        self._ensure_real()
2059
3231
    def network_name(self):
2060
3232
        return self._network_name
2061
3233
 
2062
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2063
 
        return a_bzrdir.open_branch(name=name, 
 
3234
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
 
3235
        return a_controldir.open_branch(name=name, 
2064
3236
            ignore_fallbacks=ignore_fallbacks)
2065
3237
 
2066
 
    def _vfs_initialize(self, a_bzrdir, name):
 
3238
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
 
3239
                        repository=None):
2067
3240
        # Initialisation when using a local bzrdir object, or a non-vfs init
2068
3241
        # method is not available on the server.
2069
3242
        # self._custom_format is always set - the start of initialize ensures
2070
3243
        # that.
2071
 
        if isinstance(a_bzrdir, RemoteBzrDir):
2072
 
            a_bzrdir._ensure_real()
2073
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2074
 
                name)
 
3244
        if isinstance(a_controldir, RemoteBzrDir):
 
3245
            a_controldir._ensure_real()
 
3246
            result = self._custom_format.initialize(a_controldir._real_bzrdir,
 
3247
                name=name, append_revisions_only=append_revisions_only,
 
3248
                repository=repository)
2075
3249
        else:
2076
3250
            # We assume the bzrdir is parameterised; it may not be.
2077
 
            result = self._custom_format.initialize(a_bzrdir, name)
2078
 
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
3251
            result = self._custom_format.initialize(a_controldir, name=name,
 
3252
                append_revisions_only=append_revisions_only,
 
3253
                repository=repository)
 
3254
        if (isinstance(a_controldir, RemoteBzrDir) and
2079
3255
            not isinstance(result, RemoteBranch)):
2080
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
3256
            result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
2081
3257
                                  name=name)
2082
3258
        return result
2083
3259
 
2084
 
    def initialize(self, a_bzrdir, name=None):
 
3260
    def initialize(self, a_controldir, name=None, repository=None,
 
3261
                   append_revisions_only=None):
 
3262
        if name is None:
 
3263
            name = a_controldir._get_selected_branch()
2085
3264
        # 1) get the network name to use.
2086
3265
        if self._custom_format:
2087
3266
            network_name = self._custom_format.network_name()
2088
3267
        else:
2089
 
            # Select the current bzrlib default and ask for that.
2090
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
3268
            # Select the current breezy default and ask for that.
 
3269
            reference_bzrdir_format = controldir.format_registry.get('default')()
2091
3270
            reference_format = reference_bzrdir_format.get_branch_format()
2092
3271
            self._custom_format = reference_format
2093
3272
            network_name = reference_format.network_name()
2094
3273
        # Being asked to create on a non RemoteBzrDir:
2095
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
2096
 
            return self._vfs_initialize(a_bzrdir, name=name)
2097
 
        medium = a_bzrdir._client._medium
 
3274
        if not isinstance(a_controldir, RemoteBzrDir):
 
3275
            return self._vfs_initialize(a_controldir, name=name,
 
3276
                append_revisions_only=append_revisions_only,
 
3277
                repository=repository)
 
3278
        medium = a_controldir._client._medium
2098
3279
        if medium._is_remote_before((1, 13)):
2099
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3280
            return self._vfs_initialize(a_controldir, name=name,
 
3281
                append_revisions_only=append_revisions_only,
 
3282
                repository=repository)
2100
3283
        # Creating on a remote bzr dir.
2101
3284
        # 2) try direct creation via RPC
2102
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2103
 
        if name is not None:
 
3285
        path = a_controldir._path_for_remote_call(a_controldir._client)
 
3286
        if name != "":
2104
3287
            # XXX JRV20100304: Support creating colocated branches
2105
3288
            raise errors.NoColocatedBranchSupport(self)
2106
 
        verb = 'BzrDir.create_branch'
 
3289
        verb = b'BzrDir.create_branch'
2107
3290
        try:
2108
 
            response = a_bzrdir._call(verb, path, network_name)
 
3291
            response = a_controldir._call(verb, path, network_name)
2109
3292
        except errors.UnknownSmartMethod:
2110
3293
            # Fallback - use vfs methods
2111
3294
            medium._remember_remote_is_before((1, 13))
2112
 
            return self._vfs_initialize(a_bzrdir, name=name)
2113
 
        if response[0] != 'ok':
 
3295
            return self._vfs_initialize(a_controldir, name=name,
 
3296
                    append_revisions_only=append_revisions_only,
 
3297
                    repository=repository)
 
3298
        if response[0] != b'ok':
2114
3299
            raise errors.UnexpectedSmartServerResponse(response)
2115
3300
        # Turn the response into a RemoteRepository object.
2116
3301
        format = RemoteBranchFormat(network_name=response[1])
2117
3302
        repo_format = response_tuple_to_repo_format(response[3:])
2118
 
        if response[2] == '':
2119
 
            repo_bzrdir = a_bzrdir
 
3303
        repo_path = response[2].decode('utf-8')
 
3304
        if repository is not None:
 
3305
            remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
 
3306
            url_diff = urlutils.relative_url(repository.user_url,
 
3307
                    remote_repo_url)
 
3308
            if url_diff != '.':
 
3309
                raise AssertionError(
 
3310
                    'repository.user_url %r does not match URL from server '
 
3311
                    'response (%r + %r)'
 
3312
                    % (repository.user_url, a_controldir.user_url, repo_path))
 
3313
            remote_repo = repository
2120
3314
        else:
2121
 
            repo_bzrdir = RemoteBzrDir(
2122
 
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2123
 
                a_bzrdir._client)
2124
 
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2125
 
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
3315
            if repo_path == '':
 
3316
                repo_bzrdir = a_controldir
 
3317
            else:
 
3318
                repo_bzrdir = RemoteBzrDir(
 
3319
                    a_controldir.root_transport.clone(repo_path), a_controldir._format,
 
3320
                    a_controldir._client)
 
3321
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
3322
        remote_branch = RemoteBranch(a_controldir, remote_repo,
2126
3323
            format=format, setup_stacking=False, name=name)
 
3324
        if append_revisions_only:
 
3325
            remote_branch.set_append_revisions_only(append_revisions_only)
2127
3326
        # XXX: We know this is a new branch, so it must have revno 0, revid
2128
3327
        # NULL_REVISION. Creating the branch locked would make this be unable
2129
3328
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2148
3347
        self._ensure_real()
2149
3348
        return self._custom_format.supports_set_append_revisions_only()
2150
3349
 
 
3350
    def _use_default_local_heads_to_fetch(self):
 
3351
        # If the branch format is a metadir format *and* its heads_to_fetch
 
3352
        # implementation is not overridden vs the base class, we can use the
 
3353
        # base class logic rather than use the heads_to_fetch RPC.  This is
 
3354
        # usually cheaper in terms of net round trips, as the last-revision and
 
3355
        # tags info fetched is cached and would be fetched anyway.
 
3356
        self._ensure_real()
 
3357
        if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
 
3358
            branch_class = self._custom_format._branch_class()
 
3359
            heads_to_fetch_impl = get_unbound_function(branch_class.heads_to_fetch)
 
3360
            if heads_to_fetch_impl is get_unbound_function(branch.Branch.heads_to_fetch):
 
3361
                return True
 
3362
        return False
 
3363
 
 
3364
 
 
3365
class RemoteBranchStore(_mod_config.IniFileStore):
 
3366
    """Branch store which attempts to use HPSS calls to retrieve branch store.
 
3367
 
 
3368
    Note that this is specific to bzr-based formats.
 
3369
    """
 
3370
 
 
3371
    def __init__(self, branch):
 
3372
        super(RemoteBranchStore, self).__init__()
 
3373
        self.branch = branch
 
3374
        self.id = "branch"
 
3375
        self._real_store = None
 
3376
 
 
3377
    def external_url(self):
 
3378
        return urlutils.join(self.branch.user_url, 'branch.conf')
 
3379
 
 
3380
    def _load_content(self):
 
3381
        path = self.branch._remote_path()
 
3382
        try:
 
3383
            response, handler = self.branch._call_expecting_body(
 
3384
                b'Branch.get_config_file', path)
 
3385
        except errors.UnknownSmartMethod:
 
3386
            self._ensure_real()
 
3387
            return self._real_store._load_content()
 
3388
        if len(response) and response[0] != b'ok':
 
3389
            raise errors.UnexpectedSmartServerResponse(response)
 
3390
        return handler.read_body_bytes()
 
3391
 
 
3392
    def _save_content(self, content):
 
3393
        path = self.branch._remote_path()
 
3394
        try:
 
3395
            response, handler = self.branch._call_with_body_bytes_expecting_body(
 
3396
                b'Branch.put_config_file', (path,
 
3397
                    self.branch._lock_token, self.branch._repo_lock_token),
 
3398
                content)
 
3399
        except errors.UnknownSmartMethod:
 
3400
            self._ensure_real()
 
3401
            return self._real_store._save_content(content)
 
3402
        handler.cancel_read_body()
 
3403
        if response != (b'ok', ):
 
3404
            raise errors.UnexpectedSmartServerResponse(response)
 
3405
 
 
3406
    def _ensure_real(self):
 
3407
        self.branch._ensure_real()
 
3408
        if self._real_store is None:
 
3409
            self._real_store = _mod_config.BranchStore(self.branch)
 
3410
 
2151
3411
 
2152
3412
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2153
3413
    """Branch stored on a server accessed by HPSS RPC.
2156
3416
    """
2157
3417
 
2158
3418
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
 
        _client=None, format=None, setup_stacking=True, name=None):
 
3419
        _client=None, format=None, setup_stacking=True, name=None,
 
3420
        possible_transports=None):
2160
3421
        """Create a RemoteBranch instance.
2161
3422
 
2162
3423
        :param real_branch: An optional local implementation of the branch
2173
3434
        # We intentionally don't call the parent class's __init__, because it
2174
3435
        # will try to assign to self.tags, which is a property in this subclass.
2175
3436
        # And the parent's __init__ doesn't do much anyway.
2176
 
        self.bzrdir = remote_bzrdir
 
3437
        self.controldir = remote_bzrdir
 
3438
        self.name = name
2177
3439
        if _client is not None:
2178
3440
            self._client = _client
2179
3441
        else:
2191
3453
            self._real_branch.repository = self.repository
2192
3454
        else:
2193
3455
            self._real_branch = None
2194
 
        # Fill out expected attributes of branch for bzrlib API users.
 
3456
        # Fill out expected attributes of branch for breezy API users.
2195
3457
        self._clear_cached_state()
2196
3458
        # TODO: deprecate self.base in favor of user_url
2197
 
        self.base = self.bzrdir.user_url
 
3459
        self.base = self.controldir.user_url
2198
3460
        self._name = name
2199
3461
        self._control_files = None
2200
3462
        self._lock_mode = None
2202
3464
        self._repo_lock_token = None
2203
3465
        self._lock_count = 0
2204
3466
        self._leave_lock = False
 
3467
        self.conf_store = None
2205
3468
        # Setup a format: note that we cannot call _ensure_real until all the
2206
3469
        # attributes above are set: This code cannot be moved higher up in this
2207
3470
        # function.
2227
3490
            hook(self)
2228
3491
        self._is_stacked = False
2229
3492
        if setup_stacking:
2230
 
            self._setup_stacking()
 
3493
            self._setup_stacking(possible_transports)
2231
3494
 
2232
 
    def _setup_stacking(self):
 
3495
    def _setup_stacking(self, possible_transports):
2233
3496
        # configure stacking into the remote repository, by reading it from
2234
3497
        # the vfs branch.
2235
3498
        try:
2236
3499
            fallback_url = self.get_stacked_on_url()
2237
 
        except (errors.NotStacked, errors.UnstackableBranchFormat,
2238
 
            errors.UnstackableRepositoryFormat), e:
 
3500
        except (errors.NotStacked, branch.UnstackableBranchFormat,
 
3501
            errors.UnstackableRepositoryFormat) as e:
2239
3502
            return
2240
3503
        self._is_stacked = True
2241
 
        self._activate_fallback_location(fallback_url)
 
3504
        if possible_transports is None:
 
3505
            possible_transports = []
 
3506
        else:
 
3507
            possible_transports = list(possible_transports)
 
3508
        possible_transports.append(self.controldir.root_transport)
 
3509
        self._activate_fallback_location(fallback_url,
 
3510
            possible_transports=possible_transports)
2242
3511
 
2243
3512
    def _get_config(self):
2244
3513
        return RemoteBranchConfig(self)
2245
3514
 
 
3515
    def _get_config_store(self):
 
3516
        if self.conf_store is None:
 
3517
            self.conf_store =  RemoteBranchStore(self)
 
3518
        return self.conf_store
 
3519
 
 
3520
    def store_uncommitted(self, creator):
 
3521
        self._ensure_real()
 
3522
        return self._real_branch.store_uncommitted(creator)
 
3523
 
 
3524
    def get_unshelver(self, tree):
 
3525
        self._ensure_real()
 
3526
        return self._real_branch.get_unshelver(tree)
 
3527
 
2246
3528
    def _get_real_transport(self):
2247
3529
        # if we try vfs access, return the real branch's vfs transport
2248
3530
        self._ensure_real()
2264
3546
            if not vfs.vfs_enabled():
2265
3547
                raise AssertionError('smart server vfs must be enabled '
2266
3548
                    'to use vfs implementation')
2267
 
            self.bzrdir._ensure_real()
2268
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
3549
            self.controldir._ensure_real()
 
3550
            self._real_branch = self.controldir._real_bzrdir.open_branch(
2269
3551
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
3552
            # The remote branch and the real branch shares the same store. If
 
3553
            # we don't, there will always be cases where one of the stores
 
3554
            # doesn't see an update made on the other.
 
3555
            self._real_branch.conf_store = self.conf_store
2270
3556
            if self.repository._real_repository is None:
2271
3557
                # Give the remote repository the matching real repo.
2272
3558
                real_repo = self._real_branch.repository
2287
3573
 
2288
3574
    def _clear_cached_state(self):
2289
3575
        super(RemoteBranch, self)._clear_cached_state()
 
3576
        self._tags_bytes = None
2290
3577
        if self._real_branch is not None:
2291
3578
            self._real_branch._clear_cached_state()
2292
3579
 
2308
3595
        # because it triggers an _ensure_real that we otherwise might not need.
2309
3596
        if self._control_files is None:
2310
3597
            self._control_files = RemoteBranchLockableFiles(
2311
 
                self.bzrdir, self._client)
 
3598
                self.controldir, self._client)
2312
3599
        return self._control_files
2313
3600
 
2314
 
    def _get_checkout_format(self):
2315
 
        self._ensure_real()
2316
 
        return self._real_branch._get_checkout_format()
2317
 
 
2318
3601
    def get_physical_lock_status(self):
2319
3602
        """See Branch.get_physical_lock_status()."""
2320
 
        # should be an API call to the server, as branches must be lockable.
2321
 
        self._ensure_real()
2322
 
        return self._real_branch.get_physical_lock_status()
 
3603
        try:
 
3604
            response = self._client.call(b'Branch.get_physical_lock_status',
 
3605
                self._remote_path())
 
3606
        except errors.UnknownSmartMethod:
 
3607
            self._ensure_real()
 
3608
            return self._real_branch.get_physical_lock_status()
 
3609
        if response[0] not in (b'yes', b'no'):
 
3610
            raise errors.UnexpectedSmartServerResponse(response)
 
3611
        return (response[0] == b'yes')
2323
3612
 
2324
3613
    def get_stacked_on_url(self):
2325
3614
        """Get the URL this branch is stacked against.
2333
3622
        try:
2334
3623
            # there may not be a repository yet, so we can't use
2335
3624
            # self._translate_error, so we can't use self._call either.
2336
 
            response = self._client.call('Branch.get_stacked_on_url',
 
3625
            response = self._client.call(b'Branch.get_stacked_on_url',
2337
3626
                self._remote_path())
2338
 
        except errors.ErrorFromSmartServer, err:
 
3627
        except errors.ErrorFromSmartServer as err:
2339
3628
            # there may not be a repository yet, so we can't call through
2340
3629
            # its _translate_error
2341
3630
            _translate_error(err, branch=self)
2342
 
        except errors.UnknownSmartMethod, err:
 
3631
        except errors.UnknownSmartMethod as err:
2343
3632
            self._ensure_real()
2344
3633
            return self._real_branch.get_stacked_on_url()
2345
 
        if response[0] != 'ok':
 
3634
        if response[0] != b'ok':
2346
3635
            raise errors.UnexpectedSmartServerResponse(response)
 
3636
        if sys.version_info[0] == 3:
 
3637
            return response[1].decode('utf-8')
2347
3638
        return response[1]
2348
3639
 
2349
3640
    def set_stacked_on_url(self, url):
2350
3641
        branch.Branch.set_stacked_on_url(self, url)
 
3642
        # We need the stacked_on_url to be visible both locally (to not query
 
3643
        # it repeatedly) and remotely (so smart verbs can get it server side)
 
3644
        # Without the following line,
 
3645
        # breezy.tests.per_branch.test_create_clone.TestCreateClone
 
3646
        # .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
 
3647
        # fails for remote branches -- vila 2012-01-04
 
3648
        self.conf_store.save_changes()
2351
3649
        if not url:
2352
3650
            self._is_stacked = False
2353
3651
        else:
2354
3652
            self._is_stacked = True
2355
 
        
 
3653
 
2356
3654
    def _vfs_get_tags_bytes(self):
2357
3655
        self._ensure_real()
2358
3656
        return self._real_branch._get_tags_bytes()
2359
3657
 
2360
3658
    def _get_tags_bytes(self):
 
3659
        with self.lock_read():
 
3660
            if self._tags_bytes is None:
 
3661
                self._tags_bytes = self._get_tags_bytes_via_hpss()
 
3662
            return self._tags_bytes
 
3663
 
 
3664
    def _get_tags_bytes_via_hpss(self):
2361
3665
        medium = self._client._medium
2362
3666
        if medium._is_remote_before((1, 13)):
2363
3667
            return self._vfs_get_tags_bytes()
2364
3668
        try:
2365
 
            response = self._call('Branch.get_tags_bytes', self._remote_path())
 
3669
            response = self._call(b'Branch.get_tags_bytes', self._remote_path())
2366
3670
        except errors.UnknownSmartMethod:
2367
3671
            medium._remember_remote_is_before((1, 13))
2368
3672
            return self._vfs_get_tags_bytes()
2373
3677
        return self._real_branch._set_tags_bytes(bytes)
2374
3678
 
2375
3679
    def _set_tags_bytes(self, bytes):
 
3680
        if self.is_locked():
 
3681
            self._tags_bytes = bytes
2376
3682
        medium = self._client._medium
2377
3683
        if medium._is_remote_before((1, 18)):
2378
3684
            self._vfs_set_tags_bytes(bytes)
2381
3687
            args = (
2382
3688
                self._remote_path(), self._lock_token, self._repo_lock_token)
2383
3689
            response = self._call_with_body_bytes(
2384
 
                'Branch.set_tags_bytes', args, bytes)
 
3690
                b'Branch.set_tags_bytes', args, bytes)
2385
3691
        except errors.UnknownSmartMethod:
2386
3692
            medium._remember_remote_is_before((1, 18))
2387
3693
            self._vfs_set_tags_bytes(bytes)
2388
3694
 
2389
3695
    def lock_read(self):
 
3696
        """Lock the branch for read operations.
 
3697
 
 
3698
        :return: A breezy.lock.LogicalLockResult.
 
3699
        """
2390
3700
        self.repository.lock_read()
2391
3701
        if not self._lock_mode:
2392
3702
            self._note_lock('r')
2396
3706
                self._real_branch.lock_read()
2397
3707
        else:
2398
3708
            self._lock_count += 1
 
3709
        return lock.LogicalLockResult(self.unlock)
2399
3710
 
2400
3711
    def _remote_lock_write(self, token):
2401
3712
        if token is None:
2402
 
            branch_token = repo_token = ''
 
3713
            branch_token = repo_token = b''
2403
3714
        else:
2404
3715
            branch_token = token
2405
 
            repo_token = self.repository.lock_write()
 
3716
            repo_token = self.repository.lock_write().repository_token
2406
3717
            self.repository.unlock()
2407
3718
        err_context = {'token': token}
2408
 
        response = self._call(
2409
 
            'Branch.lock_write', self._remote_path(), branch_token,
2410
 
            repo_token or '', **err_context)
2411
 
        if response[0] != 'ok':
 
3719
        try:
 
3720
            response = self._call(
 
3721
                b'Branch.lock_write', self._remote_path(), branch_token,
 
3722
                repo_token or b'', **err_context)
 
3723
        except errors.LockContention as e:
 
3724
            # The LockContention from the server doesn't have any
 
3725
            # information about the lock_url. We re-raise LockContention
 
3726
            # with valid lock_url.
 
3727
            raise errors.LockContention('(remote lock)',
 
3728
                self.repository.base.split('.bzr/')[0])
 
3729
        if response[0] != b'ok':
2412
3730
            raise errors.UnexpectedSmartServerResponse(response)
2413
3731
        ok, branch_token, repo_token = response
2414
3732
        return branch_token, repo_token
2434
3752
            self._lock_mode = 'w'
2435
3753
            self._lock_count = 1
2436
3754
        elif self._lock_mode == 'r':
2437
 
            raise errors.ReadOnlyTransaction
 
3755
            raise errors.ReadOnlyError(self)
2438
3756
        else:
2439
3757
            if token is not None:
2440
3758
                # A token was given to lock_write, and we're relocking, so
2445
3763
            self._lock_count += 1
2446
3764
            # Re-lock the repository too.
2447
3765
            self.repository.lock_write(self._repo_lock_token)
2448
 
        return self._lock_token or None
 
3766
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2449
3767
 
2450
3768
    def _unlock(self, branch_token, repo_token):
2451
3769
        err_context = {'token': str((branch_token, repo_token))}
2452
3770
        response = self._call(
2453
 
            'Branch.unlock', self._remote_path(), branch_token,
2454
 
            repo_token or '', **err_context)
2455
 
        if response == ('ok',):
 
3771
            b'Branch.unlock', self._remote_path(), branch_token,
 
3772
            repo_token or b'', **err_context)
 
3773
        if response == (b'ok',):
2456
3774
            return
2457
3775
        raise errors.UnexpectedSmartServerResponse(response)
2458
3776
 
2461
3779
        try:
2462
3780
            self._lock_count -= 1
2463
3781
            if not self._lock_count:
 
3782
                if self.conf_store is not None:
 
3783
                    self.conf_store.save_changes()
2464
3784
                self._clear_cached_state()
2465
3785
                mode = self._lock_mode
2466
3786
                self._lock_mode = None
2489
3809
            self.repository.unlock()
2490
3810
 
2491
3811
    def break_lock(self):
2492
 
        self._ensure_real()
2493
 
        return self._real_branch.break_lock()
 
3812
        try:
 
3813
            response = self._call(
 
3814
                b'Branch.break_lock', self._remote_path())
 
3815
        except errors.UnknownSmartMethod:
 
3816
            self._ensure_real()
 
3817
            return self._real_branch.break_lock()
 
3818
        if response != (b'ok',):
 
3819
            raise errors.UnexpectedSmartServerResponse(response)
2494
3820
 
2495
3821
    def leave_lock_in_place(self):
2496
3822
        if not self._lock_token:
2502
3828
            raise NotImplementedError(self.dont_leave_lock_in_place)
2503
3829
        self._leave_lock = False
2504
3830
 
2505
 
    @needs_read_lock
2506
3831
    def get_rev_id(self, revno, history=None):
2507
3832
        if revno == 0:
2508
3833
            return _mod_revision.NULL_REVISION
2509
 
        last_revision_info = self.last_revision_info()
2510
 
        ok, result = self.repository.get_rev_id_for_revno(
2511
 
            revno, last_revision_info)
2512
 
        if ok:
2513
 
            return result
2514
 
        missing_parent = result[1]
2515
 
        # Either the revision named by the server is missing, or its parent
2516
 
        # is.  Call get_parent_map to determine which, so that we report a
2517
 
        # useful error.
2518
 
        parent_map = self.repository.get_parent_map([missing_parent])
2519
 
        if missing_parent in parent_map:
2520
 
            missing_parent = parent_map[missing_parent]
2521
 
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
3834
        with self.lock_read():
 
3835
            last_revision_info = self.last_revision_info()
 
3836
            ok, result = self.repository.get_rev_id_for_revno(
 
3837
                revno, last_revision_info)
 
3838
            if ok:
 
3839
                return result
 
3840
            missing_parent = result[1]
 
3841
            # Either the revision named by the server is missing, or its parent
 
3842
            # is.  Call get_parent_map to determine which, so that we report a
 
3843
            # useful error.
 
3844
            parent_map = self.repository.get_parent_map([missing_parent])
 
3845
            if missing_parent in parent_map:
 
3846
                missing_parent = parent_map[missing_parent]
 
3847
            raise errors.RevisionNotPresent(missing_parent, self.repository)
2522
3848
 
2523
 
    def _last_revision_info(self):
2524
 
        response = self._call('Branch.last_revision_info', self._remote_path())
2525
 
        if response[0] != 'ok':
 
3849
    def _read_last_revision_info(self):
 
3850
        response = self._call(b'Branch.last_revision_info', self._remote_path())
 
3851
        if response[0] != b'ok':
2526
3852
            raise SmartProtocolError('unexpected response code %s' % (response,))
2527
3853
        revno = int(response[1])
2528
3854
        last_revision = response[2]
2534
3860
            self._ensure_real()
2535
3861
            return self._real_branch._gen_revision_history()
2536
3862
        response_tuple, response_handler = self._call_expecting_body(
2537
 
            'Branch.revision_history', self._remote_path())
2538
 
        if response_tuple[0] != 'ok':
 
3863
            b'Branch.revision_history', self._remote_path())
 
3864
        if response_tuple[0] != b'ok':
2539
3865
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2540
 
        result = response_handler.read_body_bytes().split('\x00')
 
3866
        result = response_handler.read_body_bytes().split(b'\x00')
2541
3867
        if result == ['']:
2542
3868
            return []
2543
3869
        return result
2544
3870
 
2545
3871
    def _remote_path(self):
2546
 
        return self.bzrdir._path_for_remote_call(self._client)
 
3872
        return self.controldir._path_for_remote_call(self._client)
2547
3873
 
2548
3874
    def _set_last_revision_descendant(self, revision_id, other_branch,
2549
3875
            allow_diverged=False, allow_overwrite_descendant=False):
2557
3883
        history = self._lefthand_history(revision_id)
2558
3884
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2559
3885
        err_context = {'other_branch': other_branch}
2560
 
        response = self._call('Branch.set_last_revision_ex',
 
3886
        response = self._call(b'Branch.set_last_revision_ex',
2561
3887
            self._remote_path(), self._lock_token, self._repo_lock_token,
2562
3888
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2563
3889
            **err_context)
2564
3890
        self._clear_cached_state()
2565
 
        if len(response) != 3 and response[0] != 'ok':
 
3891
        if len(response) != 3 and response[0] != b'ok':
2566
3892
            raise errors.UnexpectedSmartServerResponse(response)
2567
3893
        new_revno, new_revision_id = response[1:]
2568
3894
        self._last_revision_info_cache = new_revno, new_revision_id
2582
3908
        history = self._lefthand_history(revision_id)
2583
3909
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2584
3910
        self._clear_cached_state()
2585
 
        response = self._call('Branch.set_last_revision',
 
3911
        response = self._call(b'Branch.set_last_revision',
2586
3912
            self._remote_path(), self._lock_token, self._repo_lock_token,
2587
3913
            revision_id)
2588
 
        if response != ('ok',):
 
3914
        if response != (b'ok',):
2589
3915
            raise errors.UnexpectedSmartServerResponse(response)
2590
3916
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2591
3917
 
2592
 
    @needs_write_lock
2593
 
    def set_revision_history(self, rev_history):
2594
 
        # Send just the tip revision of the history; the server will generate
2595
 
        # the full history from that.  If the revision doesn't exist in this
2596
 
        # branch, NoSuchRevision will be raised.
2597
 
        if rev_history == []:
2598
 
            rev_id = 'null:'
2599
 
        else:
2600
 
            rev_id = rev_history[-1]
2601
 
        self._set_last_revision(rev_id)
2602
 
        for hook in branch.Branch.hooks['set_rh']:
2603
 
            hook(self, rev_history)
2604
 
        self._cache_revision_history(rev_history)
2605
 
 
2606
3918
    def _get_parent_location(self):
2607
3919
        medium = self._client._medium
2608
3920
        if medium._is_remote_before((1, 13)):
2609
3921
            return self._vfs_get_parent_location()
2610
3922
        try:
2611
 
            response = self._call('Branch.get_parent', self._remote_path())
 
3923
            response = self._call(b'Branch.get_parent', self._remote_path())
2612
3924
        except errors.UnknownSmartMethod:
2613
3925
            medium._remember_remote_is_before((1, 13))
2614
3926
            return self._vfs_get_parent_location()
2615
3927
        if len(response) != 1:
2616
3928
            raise errors.UnexpectedSmartServerResponse(response)
2617
3929
        parent_location = response[0]
2618
 
        if parent_location == '':
 
3930
        if parent_location == b'':
2619
3931
            return None
2620
 
        return parent_location
 
3932
        return parent_location.decode('utf-8')
2621
3933
 
2622
3934
    def _vfs_get_parent_location(self):
2623
3935
        self._ensure_real()
2629
3941
            return self._vfs_set_parent_location(url)
2630
3942
        try:
2631
3943
            call_url = url or ''
2632
 
            if type(call_url) is not str:
 
3944
            if not isinstance(call_url, str):
2633
3945
                raise AssertionError('url must be a str or None (%s)' % url)
2634
 
            response = self._call('Branch.set_parent_location',
 
3946
            response = self._call(b'Branch.set_parent_location',
2635
3947
                self._remote_path(), self._lock_token, self._repo_lock_token,
2636
 
                call_url)
 
3948
                call_url.encode('utf-8'))
2637
3949
        except errors.UnknownSmartMethod:
2638
3950
            medium._remember_remote_is_before((1, 15))
2639
3951
            return self._vfs_set_parent_location(url)
2644
3956
        self._ensure_real()
2645
3957
        return self._real_branch._set_parent_location(url)
2646
3958
 
2647
 
    @needs_write_lock
2648
3959
    def pull(self, source, overwrite=False, stop_revision=None,
2649
3960
             **kwargs):
2650
 
        self._clear_cached_state_of_remote_branch_only()
2651
 
        self._ensure_real()
2652
 
        return self._real_branch.pull(
2653
 
            source, overwrite=overwrite, stop_revision=stop_revision,
2654
 
            _override_hook_target=self, **kwargs)
2655
 
 
2656
 
    @needs_read_lock
2657
 
    def push(self, target, overwrite=False, stop_revision=None):
2658
 
        self._ensure_real()
2659
 
        return self._real_branch.push(
2660
 
            target, overwrite=overwrite, stop_revision=stop_revision,
2661
 
            _override_hook_source_branch=self)
 
3961
        with self.lock_write():
 
3962
            self._clear_cached_state_of_remote_branch_only()
 
3963
            self._ensure_real()
 
3964
            return self._real_branch.pull(
 
3965
                source, overwrite=overwrite, stop_revision=stop_revision,
 
3966
                _override_hook_target=self, **kwargs)
 
3967
 
 
3968
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
 
3969
        with self.lock_read():
 
3970
            self._ensure_real()
 
3971
            return self._real_branch.push(
 
3972
                target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
 
3973
                _override_hook_source_branch=self)
 
3974
 
 
3975
    def peek_lock_mode(self):
 
3976
        return self._lock_mode
2662
3977
 
2663
3978
    def is_locked(self):
2664
3979
        return self._lock_count >= 1
2665
3980
 
2666
 
    @needs_read_lock
 
3981
    def revision_id_to_dotted_revno(self, revision_id):
 
3982
        """Given a revision id, return its dotted revno.
 
3983
 
 
3984
        :return: a tuple like (1,) or (400,1,3).
 
3985
        """
 
3986
        with self.lock_read():
 
3987
            try:
 
3988
                response = self._call(b'Branch.revision_id_to_revno',
 
3989
                    self._remote_path(), revision_id)
 
3990
            except errors.UnknownSmartMethod:
 
3991
                self._ensure_real()
 
3992
                return self._real_branch.revision_id_to_dotted_revno(revision_id)
 
3993
            if response[0] == b'ok':
 
3994
                return tuple([int(x) for x in response[1:]])
 
3995
            else:
 
3996
                raise errors.UnexpectedSmartServerResponse(response)
 
3997
 
2667
3998
    def revision_id_to_revno(self, revision_id):
2668
 
        self._ensure_real()
2669
 
        return self._real_branch.revision_id_to_revno(revision_id)
2670
 
 
2671
 
    @needs_write_lock
 
3999
        """Given a revision id on the branch mainline, return its revno.
 
4000
 
 
4001
        :return: an integer
 
4002
        """
 
4003
        with self.lock_read():
 
4004
            try:
 
4005
                response = self._call(b'Branch.revision_id_to_revno',
 
4006
                    self._remote_path(), revision_id)
 
4007
            except errors.UnknownSmartMethod:
 
4008
                self._ensure_real()
 
4009
                return self._real_branch.revision_id_to_revno(revision_id)
 
4010
            if response[0] == b'ok':
 
4011
                if len(response) == 2:
 
4012
                    return int(response[1])
 
4013
                raise NoSuchRevision(self, revision_id)
 
4014
            else:
 
4015
                raise errors.UnexpectedSmartServerResponse(response)
 
4016
 
2672
4017
    def set_last_revision_info(self, revno, revision_id):
2673
 
        # XXX: These should be returned by the set_last_revision_info verb
2674
 
        old_revno, old_revid = self.last_revision_info()
2675
 
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
 
        revision_id = ensure_null(revision_id)
2677
 
        try:
2678
 
            response = self._call('Branch.set_last_revision_info',
2679
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
2680
 
                str(revno), revision_id)
2681
 
        except errors.UnknownSmartMethod:
2682
 
            self._ensure_real()
2683
 
            self._clear_cached_state_of_remote_branch_only()
2684
 
            self._real_branch.set_last_revision_info(revno, revision_id)
2685
 
            self._last_revision_info_cache = revno, revision_id
2686
 
            return
2687
 
        if response == ('ok',):
2688
 
            self._clear_cached_state()
2689
 
            self._last_revision_info_cache = revno, revision_id
2690
 
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2691
 
            # Update the _real_branch's cache too.
2692
 
            if self._real_branch is not None:
2693
 
                cache = self._last_revision_info_cache
2694
 
                self._real_branch._last_revision_info_cache = cache
2695
 
        else:
2696
 
            raise errors.UnexpectedSmartServerResponse(response)
 
4018
        with self.lock_write():
 
4019
            # XXX: These should be returned by the set_last_revision_info verb
 
4020
            old_revno, old_revid = self.last_revision_info()
 
4021
            self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
4022
            if not revision_id or not isinstance(revision_id, bytes):
 
4023
                raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
4024
            try:
 
4025
                response = self._call(b'Branch.set_last_revision_info',
 
4026
                    self._remote_path(), self._lock_token, self._repo_lock_token,
 
4027
                    str(revno).encode('ascii'), revision_id)
 
4028
            except errors.UnknownSmartMethod:
 
4029
                self._ensure_real()
 
4030
                self._clear_cached_state_of_remote_branch_only()
 
4031
                self._real_branch.set_last_revision_info(revno, revision_id)
 
4032
                self._last_revision_info_cache = revno, revision_id
 
4033
                return
 
4034
            if response == (b'ok',):
 
4035
                self._clear_cached_state()
 
4036
                self._last_revision_info_cache = revno, revision_id
 
4037
                self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
4038
                # Update the _real_branch's cache too.
 
4039
                if self._real_branch is not None:
 
4040
                    cache = self._last_revision_info_cache
 
4041
                    self._real_branch._last_revision_info_cache = cache
 
4042
            else:
 
4043
                raise errors.UnexpectedSmartServerResponse(response)
2697
4044
 
2698
 
    @needs_write_lock
2699
4045
    def generate_revision_history(self, revision_id, last_rev=None,
2700
4046
                                  other_branch=None):
2701
 
        medium = self._client._medium
2702
 
        if not medium._is_remote_before((1, 6)):
2703
 
            # Use a smart method for 1.6 and above servers
2704
 
            try:
2705
 
                self._set_last_revision_descendant(revision_id, other_branch,
2706
 
                    allow_diverged=True, allow_overwrite_descendant=True)
2707
 
                return
2708
 
            except errors.UnknownSmartMethod:
2709
 
                medium._remember_remote_is_before((1, 6))
2710
 
        self._clear_cached_state_of_remote_branch_only()
2711
 
        self.set_revision_history(self._lefthand_history(revision_id,
2712
 
            last_rev=last_rev,other_branch=other_branch))
 
4047
        with self.lock_write():
 
4048
            medium = self._client._medium
 
4049
            if not medium._is_remote_before((1, 6)):
 
4050
                # Use a smart method for 1.6 and above servers
 
4051
                try:
 
4052
                    self._set_last_revision_descendant(revision_id, other_branch,
 
4053
                        allow_diverged=True, allow_overwrite_descendant=True)
 
4054
                    return
 
4055
                except errors.UnknownSmartMethod:
 
4056
                    medium._remember_remote_is_before((1, 6))
 
4057
            self._clear_cached_state_of_remote_branch_only()
 
4058
            graph = self.repository.get_graph()
 
4059
            (last_revno, last_revid) = self.last_revision_info()
 
4060
            known_revision_ids = [
 
4061
                (last_revid, last_revno),
 
4062
                (_mod_revision.NULL_REVISION, 0),
 
4063
                ]
 
4064
            if last_rev is not None:
 
4065
                if not graph.is_ancestor(last_rev, revision_id):
 
4066
                    # our previous tip is not merged into stop_revision
 
4067
                    raise errors.DivergedBranches(self, other_branch)
 
4068
            revno = graph.find_distance_to_null(revision_id, known_revision_ids)
 
4069
            self.set_last_revision_info(revno, revision_id)
2713
4070
 
2714
4071
    def set_push_location(self, location):
 
4072
        self._set_config_location('push_location', location)
 
4073
 
 
4074
    def heads_to_fetch(self):
 
4075
        if self._format._use_default_local_heads_to_fetch():
 
4076
            # We recognise this format, and its heads-to-fetch implementation
 
4077
            # is the default one (tip + tags).  In this case it's cheaper to
 
4078
            # just use the default implementation rather than a special RPC as
 
4079
            # the tip and tags data is cached.
 
4080
            return branch.Branch.heads_to_fetch(self)
 
4081
        medium = self._client._medium
 
4082
        if medium._is_remote_before((2, 4)):
 
4083
            return self._vfs_heads_to_fetch()
 
4084
        try:
 
4085
            return self._rpc_heads_to_fetch()
 
4086
        except errors.UnknownSmartMethod:
 
4087
            medium._remember_remote_is_before((2, 4))
 
4088
            return self._vfs_heads_to_fetch()
 
4089
 
 
4090
    def _rpc_heads_to_fetch(self):
 
4091
        response = self._call(b'Branch.heads_to_fetch', self._remote_path())
 
4092
        if len(response) != 2:
 
4093
            raise errors.UnexpectedSmartServerResponse(response)
 
4094
        must_fetch, if_present_fetch = response
 
4095
        return set(must_fetch), set(if_present_fetch)
 
4096
 
 
4097
    def _vfs_heads_to_fetch(self):
2715
4098
        self._ensure_real()
2716
 
        return self._real_branch.set_push_location(location)
 
4099
        return self._real_branch.heads_to_fetch()
2717
4100
 
2718
4101
 
2719
4102
class RemoteConfig(object):
2721
4104
 
2722
4105
    It is a low-level object that considers config data to be name/value pairs
2723
4106
    that may be associated with a section. Assigning meaning to the these
2724
 
    values is done at higher levels like bzrlib.config.TreeConfig.
 
4107
    values is done at higher levels like breezy.config.TreeConfig.
2725
4108
    """
2726
4109
 
2727
4110
    def get_option(self, name, section=None, default=None):
2734
4117
        """
2735
4118
        try:
2736
4119
            configobj = self._get_configobj()
 
4120
            section_obj = None
2737
4121
            if section is None:
2738
4122
                section_obj = configobj
2739
4123
            else:
2740
4124
                try:
2741
4125
                    section_obj = configobj[section]
2742
4126
                except KeyError:
2743
 
                    return default
2744
 
            return section_obj.get(name, default)
 
4127
                    pass
 
4128
            if section_obj is None:
 
4129
                value = default
 
4130
            else:
 
4131
                value = section_obj.get(name, default)
2745
4132
        except errors.UnknownSmartMethod:
2746
 
            return self._vfs_get_option(name, section, default)
 
4133
            value = self._vfs_get_option(name, section, default)
 
4134
        for hook in _mod_config.OldConfigHooks['get']:
 
4135
            hook(self, name, value)
 
4136
        return value
2747
4137
 
2748
4138
    def _response_to_configobj(self, response):
2749
 
        if len(response[0]) and response[0][0] != 'ok':
 
4139
        if len(response[0]) and response[0][0] != b'ok':
2750
4140
            raise errors.UnexpectedSmartServerResponse(response)
2751
4141
        lines = response[1].read_body_bytes().splitlines()
2752
 
        return config.ConfigObj(lines, encoding='utf-8')
 
4142
        conf = _mod_config.ConfigObj(lines, encoding='utf-8')
 
4143
        for hook in _mod_config.OldConfigHooks['load']:
 
4144
            hook(self)
 
4145
        return conf
2753
4146
 
2754
4147
 
2755
4148
class RemoteBranchConfig(RemoteConfig):
2761
4154
    def _get_configobj(self):
2762
4155
        path = self._branch._remote_path()
2763
4156
        response = self._branch._client.call_expecting_body(
2764
 
            'Branch.get_config_file', path)
 
4157
            b'Branch.get_config_file', path)
2765
4158
        return self._response_to_configobj(response)
2766
4159
 
2767
4160
    def set_option(self, value, name, section=None):
2774
4167
        medium = self._branch._client._medium
2775
4168
        if medium._is_remote_before((1, 14)):
2776
4169
            return self._vfs_set_option(value, name, section)
 
4170
        if isinstance(value, dict):
 
4171
            if medium._is_remote_before((2, 2)):
 
4172
                return self._vfs_set_option(value, name, section)
 
4173
            return self._set_config_option_dict(value, name, section)
 
4174
        else:
 
4175
            return self._set_config_option(value, name, section)
 
4176
 
 
4177
    def _set_config_option(self, value, name, section):
2777
4178
        try:
2778
4179
            path = self._branch._remote_path()
2779
 
            response = self._branch._client.call('Branch.set_config_option',
 
4180
            response = self._branch._client.call(b'Branch.set_config_option',
2780
4181
                path, self._branch._lock_token, self._branch._repo_lock_token,
2781
 
                value.encode('utf8'), name, section or '')
 
4182
                value.encode('utf8'), name.encode('utf-8'),
 
4183
                (section or '').encode('utf-8'))
2782
4184
        except errors.UnknownSmartMethod:
 
4185
            medium = self._branch._client._medium
2783
4186
            medium._remember_remote_is_before((1, 14))
2784
4187
            return self._vfs_set_option(value, name, section)
2785
4188
        if response != ():
2786
4189
            raise errors.UnexpectedSmartServerResponse(response)
2787
4190
 
 
4191
    def _serialize_option_dict(self, option_dict):
 
4192
        utf8_dict = {}
 
4193
        for key, value in option_dict.items():
 
4194
            if isinstance(key, text_type):
 
4195
                key = key.encode('utf8')
 
4196
            if isinstance(value, text_type):
 
4197
                value = value.encode('utf8')
 
4198
            utf8_dict[key] = value
 
4199
        return bencode.bencode(utf8_dict)
 
4200
 
 
4201
    def _set_config_option_dict(self, value, name, section):
 
4202
        try:
 
4203
            path = self._branch._remote_path()
 
4204
            serialised_dict = self._serialize_option_dict(value)
 
4205
            response = self._branch._client.call(
 
4206
                b'Branch.set_config_option_dict',
 
4207
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
4208
                serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
 
4209
        except errors.UnknownSmartMethod:
 
4210
            medium = self._branch._client._medium
 
4211
            medium._remember_remote_is_before((2, 2))
 
4212
            return self._vfs_set_option(value, name, section)
 
4213
        if response != ():
 
4214
            raise errors.UnexpectedSmartServerResponse(response)
 
4215
 
2788
4216
    def _real_object(self):
2789
4217
        self._branch._ensure_real()
2790
4218
        return self._branch._real_branch
2802
4230
 
2803
4231
    def _get_configobj(self):
2804
4232
        medium = self._bzrdir._client._medium
2805
 
        verb = 'BzrDir.get_config_file'
 
4233
        verb = b'BzrDir.get_config_file'
2806
4234
        if medium._is_remote_before((1, 15)):
2807
4235
            raise errors.UnknownSmartMethod(verb)
2808
4236
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2829
4257
        return self._bzrdir._real_bzrdir
2830
4258
 
2831
4259
 
2832
 
 
2833
 
def _extract_tar(tar, to_dir):
2834
 
    """Extract all the contents of a tarfile object.
2835
 
 
2836
 
    A replacement for extractall, which is not present in python2.4
2837
 
    """
2838
 
    for tarinfo in tar:
2839
 
        tar.extract(tarinfo, to_dir)
 
4260
error_translators = registry.Registry()
 
4261
no_context_error_translators = registry.Registry()
2840
4262
 
2841
4263
 
2842
4264
def _translate_error(err, **context):
2856
4278
    def find(name):
2857
4279
        try:
2858
4280
            return context[name]
2859
 
        except KeyError, key_err:
2860
 
            mutter('Missing key %r in context %r', key_err.args[0], context)
 
4281
        except KeyError:
 
4282
            mutter('Missing key \'%s\' in context %r', name, context)
2861
4283
            raise err
2862
4284
    def get_path():
2863
4285
        """Get the path from the context if present, otherwise use first error
2865
4287
        """
2866
4288
        try:
2867
4289
            return context['path']
2868
 
        except KeyError, key_err:
 
4290
        except KeyError:
2869
4291
            try:
2870
 
                return err.error_args[0]
2871
 
            except IndexError, idx_err:
2872
 
                mutter(
2873
 
                    'Missing key %r in context %r', key_err.args[0], context)
 
4292
                return err.error_args[0].decode('utf-8')
 
4293
            except IndexError:
 
4294
                mutter('Missing key \'path\' in context %r', context)
2874
4295
                raise err
2875
 
 
2876
 
    if err.error_verb == 'IncompatibleRepositories':
2877
 
        raise errors.IncompatibleRepositories(err.error_args[0],
2878
 
            err.error_args[1], err.error_args[2])
2879
 
    elif err.error_verb == 'NoSuchRevision':
2880
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
2881
 
    elif err.error_verb == 'nosuchrevision':
2882
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
2883
 
    elif err.error_verb == 'nobranch':
2884
 
        if len(err.error_args) >= 1:
2885
 
            extra = err.error_args[0]
2886
 
        else:
2887
 
            extra = None
2888
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2889
 
            detail=extra)
2890
 
    elif err.error_verb == 'norepository':
2891
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
2892
 
    elif err.error_verb == 'LockContention':
2893
 
        raise errors.LockContention('(remote lock)')
2894
 
    elif err.error_verb == 'UnlockableTransport':
2895
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
2896
 
    elif err.error_verb == 'LockFailed':
2897
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
2898
 
    elif err.error_verb == 'TokenMismatch':
2899
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
2900
 
    elif err.error_verb == 'Diverged':
2901
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
 
    elif err.error_verb == 'TipChangeRejected':
2903
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2904
 
    elif err.error_verb == 'UnstackableBranchFormat':
2905
 
        raise errors.UnstackableBranchFormat(*err.error_args)
2906
 
    elif err.error_verb == 'UnstackableRepositoryFormat':
2907
 
        raise errors.UnstackableRepositoryFormat(*err.error_args)
2908
 
    elif err.error_verb == 'NotStacked':
2909
 
        raise errors.NotStacked(branch=find('branch'))
2910
 
    elif err.error_verb == 'PermissionDenied':
2911
 
        path = get_path()
2912
 
        if len(err.error_args) >= 2:
2913
 
            extra = err.error_args[1]
2914
 
        else:
2915
 
            extra = None
2916
 
        raise errors.PermissionDenied(path, extra=extra)
2917
 
    elif err.error_verb == 'ReadError':
2918
 
        path = get_path()
2919
 
        raise errors.ReadError(path)
2920
 
    elif err.error_verb == 'NoSuchFile':
2921
 
        path = get_path()
2922
 
        raise errors.NoSuchFile(path)
2923
 
    elif err.error_verb == 'FileExists':
2924
 
        raise errors.FileExists(err.error_args[0])
2925
 
    elif err.error_verb == 'DirectoryNotEmpty':
2926
 
        raise errors.DirectoryNotEmpty(err.error_args[0])
2927
 
    elif err.error_verb == 'ShortReadvError':
2928
 
        args = err.error_args
2929
 
        raise errors.ShortReadvError(
2930
 
            args[0], int(args[1]), int(args[2]), int(args[3]))
2931
 
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2932
 
        encoding = str(err.error_args[0]) # encoding must always be a string
2933
 
        val = err.error_args[1]
2934
 
        start = int(err.error_args[2])
2935
 
        end = int(err.error_args[3])
2936
 
        reason = str(err.error_args[4]) # reason must always be a string
 
4296
    if not isinstance(err.error_verb, bytes):
 
4297
        raise TypeError(err.error_verb)
 
4298
    try:
 
4299
        translator = error_translators.get(err.error_verb)
 
4300
    except KeyError:
 
4301
        pass
 
4302
    else:
 
4303
        raise translator(err, find, get_path)
 
4304
    try:
 
4305
        translator = no_context_error_translators.get(err.error_verb)
 
4306
    except KeyError:
 
4307
        raise errors.UnknownErrorFromSmartServer(err)
 
4308
    else:
 
4309
        raise translator(err)
 
4310
 
 
4311
 
 
4312
error_translators.register(b'NoSuchRevision',
 
4313
    lambda err, find, get_path: NoSuchRevision(
 
4314
        find('branch'), err.error_args[0]))
 
4315
error_translators.register(b'nosuchrevision',
 
4316
    lambda err, find, get_path: NoSuchRevision(
 
4317
        find('repository'), err.error_args[0]))
 
4318
 
 
4319
def _translate_nobranch_error(err, find, get_path):
 
4320
    if len(err.error_args) >= 1:
 
4321
        extra = err.error_args[0].decode('utf-8')
 
4322
    else:
 
4323
        extra = None
 
4324
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
4325
        detail=extra)
 
4326
 
 
4327
error_translators.register(b'nobranch', _translate_nobranch_error)
 
4328
error_translators.register(b'norepository',
 
4329
    lambda err, find, get_path: errors.NoRepositoryPresent(
 
4330
        find('bzrdir')))
 
4331
error_translators.register(b'UnlockableTransport',
 
4332
    lambda err, find, get_path: errors.UnlockableTransport(
 
4333
        find('bzrdir').root_transport))
 
4334
error_translators.register(b'TokenMismatch',
 
4335
    lambda err, find, get_path: errors.TokenMismatch(
 
4336
        find('token'), '(remote token)'))
 
4337
error_translators.register(b'Diverged',
 
4338
    lambda err, find, get_path: errors.DivergedBranches(
 
4339
        find('branch'), find('other_branch')))
 
4340
error_translators.register(b'NotStacked',
 
4341
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
 
4342
 
 
4343
def _translate_PermissionDenied(err, find, get_path):
 
4344
    path = get_path()
 
4345
    if len(err.error_args) >= 2:
 
4346
        extra = err.error_args[1].decode('utf-8')
 
4347
    else:
 
4348
        extra = None
 
4349
    return errors.PermissionDenied(path, extra=extra)
 
4350
 
 
4351
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
 
4352
error_translators.register(b'ReadError',
 
4353
    lambda err, find, get_path: errors.ReadError(get_path()))
 
4354
error_translators.register(b'NoSuchFile',
 
4355
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
 
4356
error_translators.register(b'TokenLockingNotSupported',
 
4357
    lambda err, find, get_path: errors.TokenLockingNotSupported(
 
4358
        find('repository')))
 
4359
error_translators.register(b'UnsuspendableWriteGroup',
 
4360
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
 
4361
        repository=find('repository')))
 
4362
error_translators.register(b'UnresumableWriteGroup',
 
4363
    lambda err, find, get_path: errors.UnresumableWriteGroup(
 
4364
        repository=find('repository'), write_groups=err.error_args[0],
 
4365
        reason=err.error_args[1]))
 
4366
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
 
4367
    lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
 
4368
no_context_error_translators.register(b'IncompatibleRepositories',
 
4369
    lambda err: errors.IncompatibleRepositories(
 
4370
        err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
 
4371
no_context_error_translators.register(b'LockContention',
 
4372
    lambda err: errors.LockContention('(remote lock)'))
 
4373
no_context_error_translators.register(b'LockFailed',
 
4374
    lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
 
4375
no_context_error_translators.register(b'TipChangeRejected',
 
4376
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
 
4377
no_context_error_translators.register(b'UnstackableBranchFormat',
 
4378
    lambda err: branch.UnstackableBranchFormat(*err.error_args))
 
4379
no_context_error_translators.register(b'UnstackableRepositoryFormat',
 
4380
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
 
4381
no_context_error_translators.register(b'FileExists',
 
4382
    lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
 
4383
no_context_error_translators.register(b'DirectoryNotEmpty',
 
4384
    lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
 
4385
no_context_error_translators.register(b'UnknownFormat',
 
4386
    lambda err: errors.UnknownFormatError(
 
4387
        err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
 
4388
 
 
4389
def _translate_short_readv_error(err):
 
4390
    args = err.error_args
 
4391
    return errors.ShortReadvError(
 
4392
            args[0].decode('utf-8'),
 
4393
            int(args[1].decode('ascii')), int(args[2].decode('ascii')),
 
4394
            int(args[3].decode('ascii')))
 
4395
 
 
4396
no_context_error_translators.register(b'ShortReadvError',
 
4397
    _translate_short_readv_error)
 
4398
 
 
4399
def _translate_unicode_error(err):
 
4400
        encoding = err.error_args[0].decode('ascii')
 
4401
        val = err.error_args[1].decode('utf-8')
 
4402
        start = int(err.error_args[2].decode('ascii'))
 
4403
        end = int(err.error_args[3].decode('ascii'))
 
4404
        reason = err.error_args[4].decode('utf-8')
2937
4405
        if val.startswith('u:'):
2938
4406
            val = val[2:].decode('utf-8')
2939
4407
        elif val.startswith('s:'):
2942
4410
            raise UnicodeDecodeError(encoding, val, start, end, reason)
2943
4411
        elif err.error_verb == 'UnicodeEncodeError':
2944
4412
            raise UnicodeEncodeError(encoding, val, start, end, reason)
2945
 
    elif err.error_verb == 'ReadOnlyError':
2946
 
        raise errors.TransportNotPossible('readonly transport')
2947
 
    raise errors.UnknownErrorFromSmartServer(err)
 
4413
 
 
4414
no_context_error_translators.register(b'UnicodeEncodeError',
 
4415
    _translate_unicode_error)
 
4416
no_context_error_translators.register(b'UnicodeDecodeError',
 
4417
    _translate_unicode_error)
 
4418
no_context_error_translators.register(b'ReadOnlyError',
 
4419
    lambda err: errors.TransportNotPossible('readonly transport'))
 
4420
no_context_error_translators.register(b'MemoryError',
 
4421
    lambda err: errors.BzrError("remote server out of memory\n"
 
4422
        "Retry non-remotely, or contact the server admin for details."))
 
4423
no_context_error_translators.register(b'RevisionNotPresent',
 
4424
    lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
 
4425
 
 
4426
no_context_error_translators.register(b'BzrCheckError',
 
4427
    lambda err: errors.BzrCheckError(msg=err.error_args[0].decode('utf-8')))
 
4428