/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/remote.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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