/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-11-16 10:50:21 UTC
  • mfrom: (7164 work)
  • mto: This revision was merged to the branch mainline in revision 7165.
  • Revision ID: jelmer@jelmer.uk-20181116105021-xl419v2rh4aus1au
Merge trunk.

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