/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-09-30 00:33:53 UTC
  • mto: This revision was merged to the branch mainline in revision 7134.
  • Revision ID: jelmer@jelmer.uk-20180930003353-2z5sugalbxfxfiru
When opening working trees with .git files, open the right control transport.

Show diffs side-by-side

added added

removed removed

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