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

  • Committer: Jelmer Vernooij
  • Date: 2017-06-10 12:50:32 UTC
  • mfrom: (6679 work)
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610125032-xb5rd5fjskjallos
Merge trunk.

Show diffs side-by-side

added added

removed removed

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