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

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import bz2
18
 
import os
19
 
import re
20
 
import sys
21
 
import zlib
22
18
 
23
 
from .. import (
 
19
from bzrlib import (
24
20
    bencode,
25
21
    branch,
26
 
    bzr as _mod_bzr,
27
 
    config as _mod_config,
28
 
    controldir,
 
22
    bzrdir,
 
23
    config,
29
24
    debug,
30
25
    errors,
31
 
    gpg,
32
26
    graph,
33
27
    lock,
34
28
    lockdir,
35
 
    osutils,
36
 
    registry,
37
 
    repository as _mod_repository,
 
29
    repository,
 
30
    revision,
38
31
    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 (
 
32
    symbol_versioning,
 
33
)
 
34
from bzrlib.branch import BranchReferenceFormat
 
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
37
from bzrlib.errors import (
53
38
    NoSuchRevision,
54
39
    SmartProtocolError,
55
40
    )
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
 
41
from bzrlib.lockable_files import LockableFiles
 
42
from bzrlib.smart import client, vfs, repository as smart_repo
 
43
from bzrlib.revision import ensure_null, NULL_REVISION
 
44
from bzrlib.trace import mutter, note, warning
70
45
 
71
46
 
72
47
class _RpcHelper(object):
75
50
    def _call(self, method, *args, **err_context):
76
51
        try:
77
52
            return self._client.call(method, *args)
78
 
        except errors.ErrorFromSmartServer as err:
 
53
        except errors.ErrorFromSmartServer, err:
79
54
            self._translate_error(err, **err_context)
80
55
 
81
56
    def _call_expecting_body(self, method, *args, **err_context):
82
57
        try:
83
58
            return self._client.call_expecting_body(method, *args)
84
 
        except errors.ErrorFromSmartServer as err:
 
59
        except errors.ErrorFromSmartServer, err:
85
60
            self._translate_error(err, **err_context)
86
61
 
87
62
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
88
63
        try:
89
64
            return self._client.call_with_body_bytes(method, args, body_bytes)
90
 
        except errors.ErrorFromSmartServer as err:
 
65
        except errors.ErrorFromSmartServer, err:
91
66
            self._translate_error(err, **err_context)
92
67
 
93
68
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
95
70
        try:
96
71
            return self._client.call_with_body_bytes_expecting_body(
97
72
                method, args, body_bytes)
98
 
        except errors.ErrorFromSmartServer as err:
 
73
        except errors.ErrorFromSmartServer, err:
99
74
            self._translate_error(err, **err_context)
100
75
 
101
76
 
102
77
def response_tuple_to_repo_format(response):
103
78
    """Convert a response tuple describing a repository format to a format."""
104
79
    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')
 
80
    format._rich_root_data = (response[0] == 'yes')
 
81
    format._supports_tree_reference = (response[1] == 'yes')
 
82
    format._supports_external_lookups = (response[2] == 'yes')
108
83
    format._network_name = response[3]
109
84
    return format
110
85
 
111
86
 
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):
 
87
# Note: RemoteBzrDirFormat is in bzrdir.py
 
88
 
 
89
class RemoteBzrDir(BzrDir, _RpcHelper):
419
90
    """Control directory on a remote server, accessed via bzr:// or similar."""
420
91
 
421
 
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
92
    def __init__(self, transport, format, _client=None):
422
93
        """Construct a RemoteBzrDir.
423
94
 
424
95
        :param _client: Private parameter for testing. Disables probing and the
425
96
            use of a real bzrdir.
426
97
        """
427
 
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
98
        BzrDir.__init__(self, transport, format)
428
99
        # this object holds a delegated bzrdir that uses file-level operations
429
100
        # to talk to the other side
430
101
        self._real_bzrdir = None
431
 
        self._has_working_tree = None
432
102
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
433
103
        # create_branch for details.
434
104
        self._next_open_branch_result = None
438
108
            self._client = client._SmartClient(medium)
439
109
        else:
440
110
            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
 
111
            return
 
112
 
451
113
        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)
 
114
        response = self._call('BzrDir.open', path)
 
115
        if response not in [('yes',), ('no',)]:
 
116
            raise errors.UnexpectedSmartServerResponse(response)
 
117
        if response == ('no',):
 
118
            raise errors.NotBranchError(path=transport.base)
482
119
 
483
120
    def _ensure_real(self):
484
121
        """Ensure that there is a _real_bzrdir set.
486
123
        Used before calls to self._real_bzrdir.
487
124
        """
488
125
        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])
 
126
            self._real_bzrdir = BzrDir.open_from_transport(
 
127
                self.root_transport, _server_formats=False)
495
128
            self._format._network_name = \
496
129
                self._real_bzrdir._format.network_name()
497
130
 
502
135
        # Prevent aliasing problems in the next_open_branch_result cache.
503
136
        # See create_branch for rationale.
504
137
        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
 
138
        return BzrDir.break_lock(self)
548
139
 
549
140
    def _vfs_cloning_metadir(self, require_stacking=False):
550
141
        self._ensure_real()
555
146
        medium = self._client._medium
556
147
        if medium._is_remote_before((1, 13)):
557
148
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
 
        verb = b'BzrDir.cloning_metadir'
 
149
        verb = 'BzrDir.cloning_metadir'
559
150
        if require_stacking:
560
 
            stacking = b'True'
 
151
            stacking = 'True'
561
152
        else:
562
 
            stacking = b'False'
 
153
            stacking = 'False'
563
154
        path = self._path_for_remote_call(self._client)
564
155
        try:
565
156
            response = self._call(verb, path, stacking)
566
157
        except errors.UnknownSmartMethod:
567
158
            medium._remember_remote_is_before((1, 13))
568
159
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
569
 
        except errors.UnknownErrorFromSmartServer as err:
570
 
            if err.error_tuple != (b'BranchReference',):
 
160
        except errors.UnknownErrorFromSmartServer, err:
 
161
            if err.error_tuple != ('BranchReference',):
571
162
                raise
572
163
            # We need to resolve the branch reference to determine the
573
164
            # cloning_metadir.  This causes unnecessary RPCs to open the
574
165
            # referenced branch (and bzrdir, etc) but only when the caller
575
166
            # didn't already resolve the branch reference.
576
167
            referenced_branch = self.open_branch()
577
 
            return referenced_branch.controldir.cloning_metadir()
 
168
            return referenced_branch.bzrdir.cloning_metadir()
578
169
        if len(response) != 3:
579
170
            raise errors.UnexpectedSmartServerResponse(response)
580
171
        control_name, repo_name, branch_info = response
581
172
        if len(branch_info) != 2:
582
173
            raise errors.UnexpectedSmartServerResponse(response)
583
174
        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
 
 
 
175
        format = bzrdir.network_format_registry.get(control_name)
590
176
        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':
 
177
            format.repository_format = repository.network_format_registry.get(
 
178
                repo_name)
 
179
        if branch_ref == 'ref':
598
180
            # XXX: we need possible_transports here to avoid reopening the
599
181
            # connection to the referenced location
600
 
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
 
182
            ref_bzrdir = BzrDir.open(branch_name)
601
183
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
602
184
            format.set_branch_format(branch_format)
603
 
        elif branch_ref == b'branch':
 
185
        elif branch_ref == 'branch':
604
186
            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)
 
187
                format.set_branch_format(
 
188
                    branch.network_format_registry.get(branch_name))
612
189
        else:
613
190
            raise errors.UnexpectedSmartServerResponse(response)
614
191
        return format
624
201
 
625
202
    def destroy_repository(self):
626
203
        """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,))
 
204
        self._ensure_real()
 
205
        self._real_bzrdir.destroy_repository()
637
206
 
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)
 
207
    def create_branch(self):
644
208
        # as per meta1 formats - just delegate to the format object which may
645
209
        # be parameterised.
646
 
        real_branch = self._format.get_branch_format().initialize(self,
647
 
                                                                  name=name, repository=repository,
648
 
                                                                  append_revisions_only=append_revisions_only)
 
210
        real_branch = self._format.get_branch_format().initialize(self)
649
211
        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)
 
212
            result = RemoteBranch(self, self.find_repository(), real_branch)
655
213
        else:
656
214
            result = real_branch
657
215
        # BzrDir.clone_on_transport() uses the result of create_branch but does
663
221
        self._next_open_branch_result = result
664
222
        return result
665
223
 
666
 
    def destroy_branch(self, name=None):
 
224
    def destroy_branch(self):
667
225
        """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
 
226
        self._ensure_real()
 
227
        self._real_bzrdir.destroy_branch()
684
228
        self._next_open_branch_result = None
685
 
        if response[0] != b'ok':
686
 
            raise SmartProtocolError(
687
 
                'unexpected response code %s' % (response,))
688
229
 
689
 
    def create_workingtree(self, revision_id=None, from_branch=None,
690
 
                           accelerator_tree=None, hardlink=False):
 
230
    def create_workingtree(self, revision_id=None, from_branch=None):
691
231
        raise errors.NotLocalUrl(self.transport.base)
692
232
 
693
 
    def find_branch_format(self, name=None):
 
233
    def find_branch_format(self):
694
234
        """Find the branch 'format' for this bzrdir.
695
235
 
696
236
        This might be a synthetic object for e.g. RemoteBranch and SVN.
697
237
        """
698
 
        b = self.open_branch(name=name)
 
238
        b = self.open_branch()
699
239
        return b._format
700
240
 
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):
 
241
    def get_branch_reference(self):
748
242
        """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
243
        response = self._get_branch_reference()
754
244
        if response[0] == 'ref':
755
 
            return response[1].decode('utf-8')
 
245
            return response[1]
756
246
        else:
757
247
            return None
758
248
 
759
249
    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
250
        path = self._path_for_remote_call(self._client)
767
251
        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
 
252
        if not medium._is_remote_before((1, 13)):
776
253
            try:
777
 
                response = self._call(verb, path)
 
254
                response = self._call('BzrDir.open_branchV2', path)
 
255
                if response[0] not in ('ref', 'branch'):
 
256
                    raise errors.UnexpectedSmartServerResponse(response)
 
257
                return response
778
258
            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'):
 
259
                medium._remember_remote_is_before((1, 13))
 
260
        response = self._call('BzrDir.open_branch', path)
 
261
        if response[0] != 'ok':
792
262
            raise errors.UnexpectedSmartServerResponse(response)
793
 
        return (response[0].decode('ascii'), response[1])
 
263
        if response[1] != '':
 
264
            return ('ref', response[1])
 
265
        else:
 
266
            return ('branch', '')
794
267
 
795
 
    def _get_tree_branch(self, name=None):
 
268
    def _get_tree_branch(self):
796
269
        """See BzrDir._get_tree_branch()."""
797
 
        return None, self.open_branch(name=name)
 
270
        return None, self.open_branch()
798
271
 
799
 
    def _open_branch(self, name, kind, location_or_format,
800
 
                     ignore_fallbacks=False, possible_transports=None):
801
 
        if kind == 'ref':
 
272
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
 
273
        if _unsupported:
 
274
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
275
        if self._next_open_branch_result is not None:
 
276
            # See create_branch for details.
 
277
            result = self._next_open_branch_result
 
278
            self._next_open_branch_result = None
 
279
            return result
 
280
        response = self._get_branch_reference()
 
281
        if response[0] == 'ref':
802
282
            # a branch reference, use the existing BranchReference logic.
803
283
            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
 
284
            return format.open(self, _found=True, location=response[1],
 
285
                ignore_fallbacks=ignore_fallbacks)
 
286
        branch_format_name = response[1]
810
287
        if not branch_format_name:
811
288
            branch_format_name = None
812
289
        format = RemoteBranchFormat(network_name=branch_format_name)
813
290
        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)
 
291
            setup_stacking=not ignore_fallbacks)
835
292
 
836
293
    def _open_repo_v1(self, path):
837
 
        verb = b'BzrDir.find_repository'
 
294
        verb = 'BzrDir.find_repository'
838
295
        response = self._call(verb, path)
839
 
        if response[0] != b'ok':
 
296
        if response[0] != 'ok':
840
297
            raise errors.UnexpectedSmartServerResponse(response)
841
298
        # servers that only support the v1 method don't support external
842
299
        # references either.
843
300
        self._ensure_real()
844
301
        repo = self._real_bzrdir.open_repository()
845
 
        response = response + (b'no', repo._format.network_name())
 
302
        response = response + ('no', repo._format.network_name())
846
303
        return response, repo
847
304
 
848
305
    def _open_repo_v2(self, path):
849
 
        verb = b'BzrDir.find_repositoryV2'
 
306
        verb = 'BzrDir.find_repositoryV2'
850
307
        response = self._call(verb, path)
851
 
        if response[0] != b'ok':
 
308
        if response[0] != 'ok':
852
309
            raise errors.UnexpectedSmartServerResponse(response)
853
310
        self._ensure_real()
854
311
        repo = self._real_bzrdir.open_repository()
856
313
        return response, repo
857
314
 
858
315
    def _open_repo_v3(self, path):
859
 
        verb = b'BzrDir.find_repositoryV3'
 
316
        verb = 'BzrDir.find_repositoryV3'
860
317
        medium = self._client._medium
861
318
        if medium._is_remote_before((1, 13)):
862
319
            raise errors.UnknownSmartMethod(verb)
865
322
        except errors.UnknownSmartMethod:
866
323
            medium._remember_remote_is_before((1, 13))
867
324
            raise
868
 
        if response[0] != b'ok':
 
325
        if response[0] != 'ok':
869
326
            raise errors.UnexpectedSmartServerResponse(response)
870
327
        return response, None
871
328
 
873
330
        path = self._path_for_remote_call(self._client)
874
331
        response = None
875
332
        for probe in [self._open_repo_v3, self._open_repo_v2,
876
 
                      self._open_repo_v1]:
 
333
            self._open_repo_v1]:
877
334
            try:
878
335
                response, real_repo = probe(path)
879
336
                break
880
337
            except errors.UnknownSmartMethod:
881
338
                pass
882
339
        if response is None:
883
 
            raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
884
 
        if response[0] != b'ok':
 
340
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
 
341
        if response[0] != 'ok':
885
342
            raise errors.UnexpectedSmartServerResponse(response)
886
343
        if len(response) != 6:
887
 
            raise SmartProtocolError(
888
 
                'incorrect response length %s' % (response,))
889
 
        if response[1] == b'':
 
344
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
345
        if response[1] == '':
890
346
            # repo is at this dir.
891
347
            format = response_tuple_to_repo_format(response[2:])
892
348
            # Used to support creating a real format instance when needed.
899
355
        else:
900
356
            raise errors.NoRepositoryPresent(self)
901
357
 
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
358
    def open_workingtree(self, recommend_upgrade=True):
918
 
        if self.has_workingtree():
 
359
        self._ensure_real()
 
360
        if self._real_bzrdir.has_workingtree():
919
361
            raise errors.NotLocalUrl(self.root_transport)
920
362
        else:
921
363
            raise errors.NoWorkingTree(self.root_transport.base)
922
364
 
923
365
    def _path_for_remote_call(self, client):
924
366
        """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
 
367
        return client.remote_path_from_transport(self.root_transport)
931
368
 
932
 
    def get_branch_transport(self, branch_format, name=None):
 
369
    def get_branch_transport(self, branch_format):
933
370
        self._ensure_real()
934
 
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
371
        return self._real_bzrdir.get_branch_transport(branch_format)
935
372
 
936
373
    def get_repository_transport(self, repository_format):
937
374
        self._ensure_real()
945
382
        """Upgrading of remote bzrdirs is not supported yet."""
946
383
        return False
947
384
 
948
 
    def needs_format_conversion(self, format):
 
385
    def needs_format_conversion(self, format=None):
949
386
        """Upgrading of remote bzrdirs is not supported yet."""
 
387
        if format is None:
 
388
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
389
                % 'needs_format_conversion(format=None)')
950
390
        return False
951
391
 
 
392
    def clone(self, url, revision_id=None, force_new_repo=False,
 
393
              preserve_stacking=False):
 
394
        self._ensure_real()
 
395
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
396
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
397
 
952
398
    def _get_config(self):
953
399
        return RemoteBzrDirConfig(self)
954
400
 
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):
 
401
 
 
402
class RemoteRepositoryFormat(repository.RepositoryFormat):
994
403
    """Format for repositories accessed over a _SmartClient.
995
404
 
996
405
    Instances of this repository are represented by RemoteRepository
1010
419
        to obtain data like the network name.
1011
420
    """
1012
421
 
1013
 
    _matchingcontroldir = RemoteBzrDirFormat()
1014
 
    supports_full_versioned_files = True
1015
 
    supports_leaving_lock = True
1016
 
    supports_overriding_transport = False
 
422
    _matchingbzrdir = RemoteBzrDirFormat()
1017
423
 
1018
424
    def __init__(self):
1019
 
        _mod_repository.RepositoryFormat.__init__(self)
 
425
        repository.RepositoryFormat.__init__(self)
1020
426
        self._custom_format = None
1021
427
        self._network_name = None
1022
428
        self._creating_bzrdir = None
1023
 
        self._revision_graph_can_have_wrong_parents = None
1024
429
        self._supports_chks = None
1025
430
        self._supports_external_lookups = None
1026
431
        self._supports_tree_reference = None
1027
 
        self._supports_funky_characters = None
1028
 
        self._supports_nesting_repositories = None
1029
432
        self._rich_root_data = None
1030
433
 
1031
434
    def __repr__(self):
1032
435
        return "%s(_network_name=%r)" % (self.__class__.__name__,
1033
 
                                         self._network_name)
 
436
            self._network_name)
1034
437
 
1035
438
    @property
1036
439
    def fast_deltas(self):
1060
463
        return self._supports_external_lookups
1061
464
 
1062
465
    @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
466
    def supports_tree_reference(self):
1080
467
        if self._supports_tree_reference is None:
1081
468
            self._ensure_real()
1083
470
                self._custom_format.supports_tree_reference
1084
471
        return self._supports_tree_reference
1085
472
 
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):
 
473
    def _vfs_initialize(self, a_bzrdir, shared):
1095
474
        """Helper for common code in initialize."""
1096
475
        if self._custom_format:
1097
476
            # Custom format requested
1098
 
            result = self._custom_format.initialize(
1099
 
                a_controldir, shared=shared)
 
477
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
1100
478
        elif self._creating_bzrdir is not None:
1101
479
            # Use the format that the repository we were created to back
1102
480
            # has.
1103
481
            prior_repo = self._creating_bzrdir.open_repository()
1104
482
            prior_repo._ensure_real()
1105
483
            result = prior_repo._real_repository._format.initialize(
1106
 
                a_controldir, shared=shared)
 
484
                a_bzrdir, shared=shared)
1107
485
        else:
1108
486
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
1109
487
            # support remote initialization.
1110
488
            # We delegate to a real object at this point (as RemoteBzrDir
1111
489
            # 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)
 
490
            # recursion if we just called a_bzrdir.create_repository.
 
491
            a_bzrdir._ensure_real()
 
492
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1115
493
        if not isinstance(result, RemoteRepository):
1116
 
            return self.open(a_controldir)
 
494
            return self.open(a_bzrdir)
1117
495
        else:
1118
496
            return result
1119
497
 
1120
 
    def initialize(self, a_controldir, shared=False):
 
498
    def initialize(self, a_bzrdir, shared=False):
1121
499
        # 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
 
500
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
501
            return self._vfs_initialize(a_bzrdir, shared)
 
502
        medium = a_bzrdir._client._medium
1125
503
        if medium._is_remote_before((1, 13)):
1126
 
            return self._vfs_initialize(a_controldir, shared)
 
504
            return self._vfs_initialize(a_bzrdir, shared)
1127
505
        # Creating on a remote bzr dir.
1128
506
        # 1) get the network name to use.
1129
507
        if self._custom_format:
1131
509
        elif self._network_name:
1132
510
            network_name = self._network_name
1133
511
        else:
1134
 
            # Select the current breezy default and ask for that.
1135
 
            reference_bzrdir_format = controldir.format_registry.get(
1136
 
                'default')()
 
512
            # Select the current bzrlib default and ask for that.
 
513
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
1137
514
            reference_format = reference_bzrdir_format.repository_format
1138
515
            network_name = reference_format.network_name()
1139
516
        # 2) try direct creation via RPC
1140
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
1141
 
        verb = b'BzrDir.create_repository'
 
517
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
518
        verb = 'BzrDir.create_repository'
1142
519
        if shared:
1143
 
            shared_str = b'True'
 
520
            shared_str = 'True'
1144
521
        else:
1145
 
            shared_str = b'False'
 
522
            shared_str = 'False'
1146
523
        try:
1147
 
            response = a_controldir._call(verb, path, network_name, shared_str)
 
524
            response = a_bzrdir._call(verb, path, network_name, shared_str)
1148
525
        except errors.UnknownSmartMethod:
1149
526
            # Fallback - use vfs methods
1150
527
            medium._remember_remote_is_before((1, 13))
1151
 
            return self._vfs_initialize(a_controldir, shared)
 
528
            return self._vfs_initialize(a_bzrdir, shared)
1152
529
        else:
1153
530
            # Turn the response into a RemoteRepository object.
1154
531
            format = response_tuple_to_repo_format(response[1:])
1155
532
            # Used to support creating a real format instance when needed.
1156
 
            format._creating_bzrdir = a_controldir
1157
 
            remote_repo = RemoteRepository(a_controldir, format)
 
533
            format._creating_bzrdir = a_bzrdir
 
534
            remote_repo = RemoteRepository(a_bzrdir, format)
1158
535
            format._creating_repo = remote_repo
1159
536
            return remote_repo
1160
537
 
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()
 
538
    def open(self, a_bzrdir):
 
539
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
540
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
541
        return a_bzrdir.open_repository()
1165
542
 
1166
543
    def _ensure_real(self):
1167
544
        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)
 
545
            self._custom_format = repository.network_format_registry.get(
 
546
                self._network_name)
1174
547
 
1175
548
    @property
1176
549
    def _fetch_order(self):
1188
561
        return self._custom_format._fetch_reconcile
1189
562
 
1190
563
    def get_format_description(self):
1191
 
        self._ensure_real()
1192
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
564
        return 'bzr remote repository'
1193
565
 
1194
566
    def __eq__(self, other):
1195
567
        return self.__class__ is other.__class__
1211
583
        return self._custom_format._serializer
1212
584
 
1213
585
 
1214
 
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1215
 
                       lock._RelockDebugMixin):
 
586
class RemoteRepository(_RpcHelper):
1216
587
    """Repository accessed over rpc.
1217
588
 
1218
589
    For the moment most operations are performed using local transport-backed
1234
605
            self._real_repository = real_repository
1235
606
        else:
1236
607
            self._real_repository = None
1237
 
        self.controldir = remote_bzrdir
 
608
        self.bzrdir = remote_bzrdir
1238
609
        if _client is None:
1239
610
            self._client = remote_bzrdir._client
1240
611
        else:
1242
613
        self._format = format
1243
614
        self._lock_mode = None
1244
615
        self._lock_token = None
1245
 
        self._write_group_tokens = None
1246
616
        self._lock_count = 0
1247
617
        self._leave_lock = False
1248
618
        # Cache of revision parents; misses are cached during read locks, and
1258
628
        self._reconcile_does_inventory_gc = False
1259
629
        self._reconcile_fixes_text_parents = False
1260
630
        self._reconcile_backsup_inventory = False
1261
 
        self.base = self.controldir.transport.base
 
631
        self.base = self.bzrdir.transport.base
1262
632
        # Additional places to query for data.
1263
633
        self._fallback_repositories = []
1264
634
 
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
635
    def __str__(self):
1277
636
        return "%s(%s)" % (self.__class__.__name__, self.base)
1278
637
 
1288
647
 
1289
648
        :param suppress_errors: see Repository.abort_write_group.
1290
649
        """
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
 
650
        self._ensure_real()
 
651
        return self._real_repository.abort_write_group(
 
652
            suppress_errors=suppress_errors)
1316
653
 
1317
654
    @property
1318
655
    def chk_bytes(self):
1332
669
        for older plugins that don't use e.g. the CommitBuilder
1333
670
        facility.
1334
671
        """
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()
 
672
        self._ensure_real()
 
673
        return self._real_repository.commit_write_group()
1348
674
 
1349
675
    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
 
676
        self._ensure_real()
 
677
        return self._real_repository.resume_write_group(tokens)
1362
678
 
1363
679
    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
 
680
        self._ensure_real()
 
681
        return self._real_repository.suspend_write_group()
1369
682
 
1370
683
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1371
684
        self._ensure_real()
1379
692
 
1380
693
    def get_rev_id_for_revno(self, revno, known_pair):
1381
694
        """See Repository.get_rev_id_for_revno."""
1382
 
        path = self.controldir._path_for_remote_call(self._client)
 
695
        path = self.bzrdir._path_for_remote_call(self._client)
1383
696
        try:
1384
697
            if self._client._medium._is_remote_before((1, 17)):
1385
698
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
1386
699
            response = self._call(
1387
 
                b'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
700
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
1388
701
        except errors.UnknownSmartMethod:
1389
702
            self._client._medium._remember_remote_is_before((1, 17))
1390
703
            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':
 
704
        if response[0] == 'ok':
1406
705
            return True, response[1]
1407
 
        elif response[0] == b'history-incomplete':
 
706
        elif response[0] == 'history-incomplete':
1408
707
            known_pair = response[1:3]
1409
708
            for fallback in self._fallback_repositories:
1410
 
                found, result = fallback.get_rev_id_for_revno(
1411
 
                    revno, known_pair)
 
709
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1412
710
                if found:
1413
711
                    return True, result
1414
712
                else:
1435
733
            if 'hpssvfs' in debug.debug_flags:
1436
734
                import traceback
1437
735
                warning('VFS Repository access triggered\n%s',
1438
 
                        ''.join(traceback.format_stack()))
 
736
                    ''.join(traceback.format_stack()))
1439
737
            self._unstacked_provider.missing_keys.clear()
1440
 
            self.controldir._ensure_real()
 
738
            self.bzrdir._ensure_real()
1441
739
            self._set_real_repository(
1442
 
                self.controldir._real_bzrdir.open_repository())
 
740
                self.bzrdir._real_bzrdir.open_repository())
1443
741
 
1444
742
    def _translate_error(self, err, **context):
1445
 
        self.controldir._translate_error(err, repository=self, **context)
 
743
        self.bzrdir._translate_error(err, repository=self, **context)
1446
744
 
1447
745
    def find_text_key_references(self):
1448
746
        """Find the text key references within the repository.
1449
747
 
 
748
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
749
        revision_ids. Each altered file-ids has the exact revision_ids that
 
750
        altered it listed explicitly.
1450
751
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1451
752
            to whether they were referred to by the inventory of the
1452
753
            revision_id that they contain. The inventory texts from all present
1469
770
    def _get_revision_graph(self, revision_id):
1470
771
        """Private method for using with old (< 1.2) servers to fallback."""
1471
772
        if revision_id is None:
1472
 
            revision_id = b''
1473
 
        elif _mod_revision.is_null(revision_id):
 
773
            revision_id = ''
 
774
        elif revision.is_null(revision_id):
1474
775
            return {}
1475
776
 
1476
 
        path = self.controldir._path_for_remote_call(self._client)
 
777
        path = self.bzrdir._path_for_remote_call(self._client)
1477
778
        response = self._call_expecting_body(
1478
 
            b'Repository.get_revision_graph', path, revision_id)
 
779
            'Repository.get_revision_graph', path, revision_id)
1479
780
        response_tuple, response_handler = response
1480
 
        if response_tuple[0] != b'ok':
 
781
        if response_tuple[0] != 'ok':
1481
782
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1482
783
        coded = response_handler.read_body_bytes()
1483
 
        if coded == b'':
 
784
        if coded == '':
1484
785
            # no revisions in this repository!
1485
786
            return {}
1486
 
        lines = coded.split(b'\n')
 
787
        lines = coded.split('\n')
1487
788
        revision_graph = {}
1488
789
        for line in lines:
1489
790
            d = tuple(line.split())
1499
800
        """Return a source for streaming from this repository."""
1500
801
        return RemoteStreamSource(self, to_format)
1501
802
 
1502
 
    def get_file_graph(self):
1503
 
        with self.lock_read():
1504
 
            return graph.Graph(self.texts)
1505
 
 
 
803
    @needs_read_lock
1506
804
    def has_revision(self, revision_id):
1507
805
        """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,))
 
806
        # Copy of bzrlib.repository.Repository.has_revision
 
807
        return revision_id in self.has_revisions((revision_id,))
1511
808
 
 
809
    @needs_read_lock
1512
810
    def has_revisions(self, revision_ids):
1513
811
        """Probe to find out the presence of multiple revisions.
1514
812
 
1515
813
        :param revision_ids: An iterable of revision_ids.
1516
814
        :return: A set of the revision_ids that were present.
1517
815
        """
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
 
816
        # Copy of bzrlib.repository.Repository.has_revisions
 
817
        parent_map = self.get_parent_map(revision_ids)
 
818
        result = set(parent_map)
 
819
        if _mod_revision.NULL_REVISION in revision_ids:
 
820
            result.add(_mod_revision.NULL_REVISION)
 
821
        return result
1525
822
 
1526
823
    def _has_same_fallbacks(self, other_repo):
1527
824
        """Returns true if the repositories have the same fallbacks."""
1528
825
        # XXX: copied from Repository; it should be unified into a base class
1529
 
        # <https://bugs.launchpad.net/bzr/+bug/401622>
 
826
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
1530
827
        my_fb = self._fallback_repositories
1531
828
        other_fb = other_repo._fallback_repositories
1532
829
        if len(my_fb) != len(other_fb):
1540
837
        # TODO: Move to RepositoryBase and unify with the regular Repository
1541
838
        # one; unfortunately the tests rely on slightly different behaviour at
1542
839
        # present -- mbp 20090710
1543
 
        return (self.__class__ is other.__class__
1544
 
                and self.controldir.transport.base == other.controldir.transport.base)
 
840
        return (self.__class__ is other.__class__ and
 
841
                self.bzrdir.transport.base == other.bzrdir.transport.base)
1545
842
 
1546
843
    def get_graph(self, other_repository=None):
1547
844
        """Return the graph for this repository format"""
1548
845
        parents_provider = self._make_parents_provider(other_repository)
1549
846
        return graph.Graph(parents_provider)
1550
847
 
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
848
    def gather_stats(self, revid=None, committers=None):
1561
849
        """See Repository.gather_stats()."""
1562
 
        path = self.controldir._path_for_remote_call(self._client)
 
850
        path = self.bzrdir._path_for_remote_call(self._client)
1563
851
        # 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''
 
852
        if revid is None or revision.is_null(revid):
 
853
            fmt_revid = ''
1566
854
        else:
1567
855
            fmt_revid = revid
1568
856
        if committers is None or not committers:
1569
 
            fmt_committers = b'no'
 
857
            fmt_committers = 'no'
1570
858
        else:
1571
 
            fmt_committers = b'yes'
 
859
            fmt_committers = 'yes'
1572
860
        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':
 
861
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
862
        if response_tuple[0] != 'ok':
1575
863
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1576
864
 
1577
865
        body = response_handler.read_body_bytes()
1578
866
        result = {}
1579
 
        for line in body.split(b'\n'):
 
867
        for line in body.split('\n'):
1580
868
            if not line:
1581
869
                continue
1582
 
            key, val_text = line.split(b':')
1583
 
            key = key.decode('ascii')
 
870
            key, val_text = line.split(':')
1584
871
            if key in ('revisions', 'size', 'committers'):
1585
872
                result[key] = int(val_text)
1586
873
            elif key in ('firstrev', 'latestrev'):
1587
 
                values = val_text.split(b' ')[1:]
1588
 
                result[key] = (float(values[0]), int(values[1]))
 
874
                values = val_text.split(' ')[1:]
 
875
                result[key] = (float(values[0]), long(values[1]))
1589
876
 
1590
877
        return result
1591
878
 
1597
884
 
1598
885
    def get_physical_lock_status(self):
1599
886
        """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')
 
887
        # should be an API call to the server.
 
888
        self._ensure_real()
 
889
        return self._real_repository.get_physical_lock_status()
1609
890
 
1610
891
    def is_in_write_group(self):
1611
892
        """Return True if there is an open write group.
1612
893
 
1613
894
        write groups are only applicable locally for the smart server..
1614
895
        """
1615
 
        if self._write_group_tokens is not None:
1616
 
            return True
1617
896
        if self._real_repository:
1618
897
            return self._real_repository.is_in_write_group()
1619
898
 
1622
901
 
1623
902
    def is_shared(self):
1624
903
        """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'
 
904
        path = self.bzrdir._path_for_remote_call(self._client)
 
905
        response = self._call('Repository.is_shared', path)
 
906
        if response[0] not in ('yes', 'no'):
 
907
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
908
        return response[0] == 'yes'
1631
909
 
1632
910
    def is_write_locked(self):
1633
911
        return self._lock_mode == 'w'
1634
912
 
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
913
    def lock_read(self):
1641
 
        """Lock the repository for read operations.
1642
 
 
1643
 
        :return: A breezy.lock.LogicalLockResult.
1644
 
        """
1645
914
        # wrong eventually - want a local lock cache context
1646
915
        if not self._lock_mode:
1647
 
            self._note_lock('r')
1648
916
            self._lock_mode = 'r'
1649
917
            self._lock_count = 1
1650
918
            self._unstacked_provider.enable_cache(cache_misses=True)
1654
922
                repo.lock_read()
1655
923
        else:
1656
924
            self._lock_count += 1
1657
 
        return lock.LogicalLockResult(self.unlock)
1658
925
 
1659
926
    def _remote_lock_write(self, token):
1660
 
        path = self.controldir._path_for_remote_call(self._client)
 
927
        path = self.bzrdir._path_for_remote_call(self._client)
1661
928
        if token is None:
1662
 
            token = b''
 
929
            token = ''
1663
930
        err_context = {'token': token}
1664
 
        response = self._call(b'Repository.lock_write', path, token,
 
931
        response = self._call('Repository.lock_write', path, token,
1665
932
                              **err_context)
1666
 
        if response[0] == b'ok':
 
933
        if response[0] == 'ok':
1667
934
            ok, token = response
1668
935
            return token
1669
936
        else:
1671
938
 
1672
939
    def lock_write(self, token=None, _skip_rpc=False):
1673
940
        if not self._lock_mode:
1674
 
            self._note_lock('w')
1675
941
            if _skip_rpc:
1676
942
                if self._lock_token is not None:
1677
943
                    if token != self._lock_token:
1700
966
            raise errors.ReadOnlyError(self)
1701
967
        else:
1702
968
            self._lock_count += 1
1703
 
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
969
        return self._lock_token or None
1704
970
 
1705
971
    def leave_lock_in_place(self):
1706
972
        if not self._lock_token:
1741
1007
        # 3) new servers, RemoteRepository.ensure_real is triggered before
1742
1008
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1743
1009
        # and need to populate it.
1744
 
        if (self._fallback_repositories
1745
 
            and len(self._real_repository._fallback_repositories)
1746
 
                != len(self._fallback_repositories)):
 
1010
        if (self._fallback_repositories and
 
1011
            len(self._real_repository._fallback_repositories) !=
 
1012
            len(self._fallback_repositories)):
1747
1013
            if len(self._real_repository._fallback_repositories):
1748
1014
                raise AssertionError(
1749
1015
                    "cannot cleanly remove existing _fallback_repositories")
1755
1021
            self._real_repository.lock_write(self._lock_token)
1756
1022
        elif self._lock_mode == 'r':
1757
1023
            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
1024
 
1763
1025
    def start_write_group(self):
1764
1026
        """Start a write group on the decorated repository.
1768
1030
        for older plugins that don't use e.g. the CommitBuilder
1769
1031
        facility.
1770
1032
        """
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]]
 
1033
        self._ensure_real()
 
1034
        return self._real_repository.start_write_group()
1789
1035
 
1790
1036
    def _unlock(self, token):
1791
 
        path = self.controldir._path_for_remote_call(self._client)
 
1037
        path = self.bzrdir._path_for_remote_call(self._client)
1792
1038
        if not token:
1793
1039
            # with no token the remote repository is not persistently locked.
1794
1040
            return
1795
1041
        err_context = {'token': token}
1796
 
        response = self._call(b'Repository.unlock', path, token,
 
1042
        response = self._call('Repository.unlock', path, token,
1797
1043
                              **err_context)
1798
 
        if response == (b'ok',):
 
1044
        if response == ('ok',):
1799
1045
            return
1800
1046
        else:
1801
1047
            raise errors.UnexpectedSmartServerResponse(response)
1802
1048
 
1803
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1804
1049
    def unlock(self):
1805
1050
        if not self._lock_count:
1806
1051
            return lock.cant_unlock_not_held(self)
1818
1063
            # This is just to let the _real_repository stay up to date.
1819
1064
            if self._real_repository is not None:
1820
1065
                self._real_repository.unlock()
1821
 
            elif self._write_group_tokens is not None:
1822
 
                self.abort_write_group()
1823
1066
        finally:
1824
1067
            # The rpc-level lock should be released even if there was a
1825
1068
            # problem releasing the vfs-based lock.
1837
1080
 
1838
1081
    def break_lock(self):
1839
1082
        # 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)
 
1083
        self._ensure_real()
 
1084
        return self._real_repository.break_lock()
1848
1085
 
1849
1086
    def _get_tarball(self, compression):
1850
1087
        """Return a TemporaryFile containing a repository tarball.
1852
1089
        Returns None if the server does not support sending tarballs.
1853
1090
        """
1854
1091
        import tempfile
1855
 
        path = self.controldir._path_for_remote_call(self._client)
 
1092
        path = self.bzrdir._path_for_remote_call(self._client)
1856
1093
        try:
1857
1094
            response, protocol = self._call_expecting_body(
1858
 
                b'Repository.tarball', path, compression.encode('ascii'))
 
1095
                'Repository.tarball', path, compression)
1859
1096
        except errors.UnknownSmartMethod:
1860
1097
            protocol.cancel_read_body()
1861
1098
            return None
1862
 
        if response[0] == b'ok':
 
1099
        if response[0] == 'ok':
1863
1100
            # Extract the tarball and return it
1864
1101
            t = tempfile.NamedTemporaryFile()
1865
1102
            # TODO: rpc layer should read directly into it...
1869
1106
        raise errors.UnexpectedSmartServerResponse(response)
1870
1107
 
1871
1108
    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()
 
1109
        # TODO: Option to control what format is created?
 
1110
        self._ensure_real()
 
1111
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
1112
                                                             shared=False)
 
1113
        dest_repo.fetch(self, revision_id=revision_id)
1893
1114
        return dest_repo
1894
1115
 
1895
 
    # These methods are just thin shims to the VFS object for now.
 
1116
    ### These methods are just thin shims to the VFS object for now.
1896
1117
 
1897
1118
    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]
 
1119
        self._ensure_real()
 
1120
        return self._real_repository.revision_tree(revision_id)
1905
1121
 
1906
1122
    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]
 
1123
        self._ensure_real()
 
1124
        return self._real_repository.get_serializer_format()
1917
1125
 
1918
1126
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1919
1127
                           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
 
1128
                           revision_id=None):
 
1129
        # FIXME: It ought to be possible to call this without immediately
 
1130
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
1131
        self._ensure_real()
 
1132
        real_repo = self._real_repository
 
1133
        builder = real_repo.get_commit_builder(branch, parents,
 
1134
                config, timestamp=timestamp, timezone=timezone,
 
1135
                committer=committer, revprops=revprops, revision_id=revision_id)
 
1136
        return builder
1944
1137
 
1945
1138
    def add_fallback_repository(self, repository):
1946
1139
        """Add a repository to use for looking up data not held locally.
1953
1146
        # We need to accumulate additional repositories here, to pass them in
1954
1147
        # on various RPC's.
1955
1148
        #
1956
 
        # Make the check before we lock: this raises an exception.
1957
 
        self._check_fallback_repository(repository)
1958
1149
        if self.is_locked():
1959
1150
            # We will call fallback.unlock() when we transition to the unlocked
1960
1151
            # state, so always add a lock here. If a caller passes us a locked
1965
1156
        # _real_branch had its get_stacked_on_url method called), then the
1966
1157
        # repository to be added may already be in the _real_repositories list.
1967
1158
        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:
 
1159
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
 
1160
                self._real_repository._fallback_repositories]
 
1161
            if repository.bzrdir.root_transport.base not in fallback_locations:
1971
1162
                self._real_repository.add_fallback_repository(repository)
1972
1163
 
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
1164
    def add_inventory(self, revid, inv, parents):
1984
1165
        self._ensure_real()
1985
1166
        return self._real_repository.add_inventory(revid, inv, parents)
1986
1167
 
1987
1168
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1988
 
                               parents, basis_inv=None, propagate_caches=False):
 
1169
                               parents):
1989
1170
        self._ensure_real()
1990
1171
        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
 
 
 
1172
            delta, new_revision_id, parents)
 
1173
 
 
1174
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
1175
        self._ensure_real()
 
1176
        return self._real_repository.add_revision(
 
1177
            rev_id, rev, inv=inv, config=config)
 
1178
 
 
1179
    @needs_read_lock
2020
1180
    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
1181
        self._ensure_real()
2069
 
        return self._real_repository._iter_inventories(revision_ids, ordering)
 
1182
        return self._real_repository.get_inventory(revision_id)
2070
1183
 
2071
1184
    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
 
 
 
1185
        self._ensure_real()
 
1186
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1187
 
 
1188
    @needs_read_lock
2147
1189
    def get_revision(self, revision_id):
2148
 
        with self.lock_read():
2149
 
            return self.get_revisions([revision_id])[0]
 
1190
        self._ensure_real()
 
1191
        return self._real_repository.get_revision(revision_id)
2150
1192
 
2151
1193
    def get_transaction(self):
2152
1194
        self._ensure_real()
2153
1195
        return self._real_repository.get_transaction()
2154
1196
 
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
 
1197
    @needs_read_lock
 
1198
    def clone(self, a_bzrdir, revision_id=None):
 
1199
        self._ensure_real()
 
1200
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2161
1201
 
2162
1202
    def make_working_trees(self):
2163
1203
        """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'
 
1204
        self._ensure_real()
 
1205
        return self._real_repository.make_working_trees()
2174
1206
 
2175
1207
    def refresh_data(self):
2176
 
        """Re-read any data needed to synchronise with disk.
 
1208
        """Re-read any data needed to to synchronise with disk.
2177
1209
 
2178
1210
        This method is intended to be called after another repository instance
2179
1211
        (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.
 
1212
        repository. It may not be called during a write group, but may be
 
1213
        called at any other time.
2185
1214
        """
 
1215
        if self.is_in_write_group():
 
1216
            raise errors.InternalBzrError(
 
1217
                "May not refresh_data while in a write group.")
2186
1218
        if self._real_repository is not None:
2187
1219
            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
1220
 
2192
1221
    def revision_ids_to_search_result(self, result_set):
2193
1222
        """Convert a set of revision ids to a graph SearchResult."""
2194
1223
        result_parents = set()
2195
 
        for parents in self.get_graph().get_parent_map(result_set).values():
 
1224
        for parents in self.get_graph().get_parent_map(
 
1225
            result_set).itervalues():
2196
1226
            result_parents.update(parents)
2197
1227
        included_keys = result_set.intersection(result_parents)
2198
1228
        start_keys = result_set.difference(included_keys)
2199
1229
        exclude_keys = result_parents.difference(result_set)
2200
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
2201
 
                                        len(result_set), result_set)
 
1230
        result = graph.SearchResult(start_keys, exclude_keys,
 
1231
            len(result_set), result_set)
2202
1232
        return result
2203
1233
 
2204
 
    def search_missing_revision_ids(self, other,
2205
 
                                    find_ghosts=True, revision_ids=None, if_present_ids=None,
2206
 
                                    limit=None):
 
1234
    @needs_read_lock
 
1235
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
2207
1236
        """Return the revision ids that other has that this does not.
2208
1237
 
2209
1238
        These are returned in topological order.
2210
1239
 
2211
1240
        revision_id: only return revision ids included by revision_id.
2212
1241
        """
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)
 
1242
        return repository.InterRepository.get(
 
1243
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
2218
1244
 
2219
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
2220
 
              fetch_spec=None, lossy=False):
 
1245
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1246
            fetch_spec=None):
2221
1247
        # No base implementation to use as RemoteRepository is not a subclass
2222
1248
        # of Repository; so this is a copy of Repository.fetch().
2223
1249
        if fetch_spec is not None and revision_id is not None:
2227
1253
            raise errors.InternalBzrError(
2228
1254
                "May not fetch while in a write group.")
2229
1255
        # 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)):
 
1256
        if (self.has_same_location(source)
 
1257
            and fetch_spec is None
 
1258
            and self._has_same_fallbacks(source)):
2233
1259
            # check that last_revision is in 'from' and then return a
2234
1260
            # no-operation.
2235
 
            if (revision_id is not None
2236
 
                    and not _mod_revision.is_null(revision_id)):
 
1261
            if (revision_id is not None and
 
1262
                not revision.is_null(revision_id)):
2237
1263
                self.get_revision(revision_id)
2238
 
            return _mod_repository.FetchResult(0)
 
1264
            return 0, []
2239
1265
        # if there is no specific appropriate InterRepository, this will get
2240
1266
        # the InterRepository base class, which raises an
2241
1267
        # 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)
 
1268
        inter = repository.InterRepository.get(source, self)
 
1269
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1270
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2250
1271
 
2251
1272
    def create_bundle(self, target, base, fileobj, format=None):
2252
1273
        self._ensure_real()
2253
1274
        self._real_repository.create_bundle(target, base, fileobj, format)
2254
1275
 
 
1276
    @needs_read_lock
 
1277
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1278
        self._ensure_real()
 
1279
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
1280
 
2255
1281
    def fileids_altered_by_revision_ids(self, revision_ids):
2256
1282
        self._ensure_real()
2257
1283
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2261
1287
        return self._real_repository._get_versioned_file_checker(
2262
1288
            revisions, revision_versions_cache)
2263
1289
 
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
1290
    def iter_files_bytes(self, desired_files):
2316
1291
        """See Repository.iter_file_bytes.
2317
1292
        """
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)
 
1293
        self._ensure_real()
 
1294
        return self._real_repository.iter_files_bytes(desired_files)
2347
1295
 
2348
1296
    def get_parent_map(self, revision_ids):
2349
 
        """See breezy.Graph.get_parent_map()."""
 
1297
        """See bzrlib.Graph.get_parent_map()."""
2350
1298
        return self._make_parents_provider().get_parent_map(revision_ids)
2351
1299
 
2352
1300
    def _get_parent_map_rpc(self, keys):
2371
1319
            # There is one other "bug" which is that ghosts in
2372
1320
            # get_revision_graph() are not returned at all. But we won't worry
2373
1321
            # about that for now.
2374
 
            for node_id, parent_ids in rg.items():
 
1322
            for node_id, parent_ids in rg.iteritems():
2375
1323
                if parent_ids == ():
2376
1324
                    rg[node_id] = (NULL_REVISION,)
2377
1325
            rg[NULL_REVISION] = ()
2382
1330
            raise ValueError('get_parent_map(None) is not valid')
2383
1331
        if NULL_REVISION in keys:
2384
1332
            keys.discard(NULL_REVISION)
2385
 
            found_parents = {NULL_REVISION: ()}
 
1333
            found_parents = {NULL_REVISION:()}
2386
1334
            if not keys:
2387
1335
                return found_parents
2388
1336
        else:
2408
1356
        if parents_map is None:
2409
1357
            # Repository is not locked, so there's no cache.
2410
1358
            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)
 
1359
        # start_set is all the keys in the cache
 
1360
        start_set = set(parents_map)
 
1361
        # result set is all the references to keys in the cache
 
1362
        result_parents = set()
 
1363
        for parents in parents_map.itervalues():
 
1364
            result_parents.update(parents)
 
1365
        stop_keys = result_parents.difference(start_set)
 
1366
        # We don't need to send ghosts back to the server as a position to
 
1367
        # stop either.
 
1368
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1369
        key_count = len(parents_map)
 
1370
        if (NULL_REVISION in result_parents
 
1371
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1372
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1373
            # in our cache of "missing" keys we need to increment our key count
 
1374
            # by 1, because the reconsitituted SearchResult on the server will
 
1375
            # still consider NULL_REVISION to be an included key.
 
1376
            key_count += 1
 
1377
        included_keys = start_set.intersection(result_parents)
 
1378
        start_set.difference_update(included_keys)
2420
1379
        recipe = ('manual', start_set, stop_keys, key_count)
2421
1380
        body = self._serialise_search_recipe(recipe)
2422
 
        path = self.controldir._path_for_remote_call(self._client)
 
1381
        path = self.bzrdir._path_for_remote_call(self._client)
2423
1382
        for key in keys:
2424
 
            if not isinstance(key, bytes):
 
1383
            if type(key) is not str:
2425
1384
                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)
 
1385
                    "key %r not a plain string" % (key,))
 
1386
        verb = 'Repository.get_parent_map'
 
1387
        args = (path, 'include-missing:') + tuple(keys)
2429
1388
        try:
2430
1389
            response = self._call_with_body_bytes_expecting_body(
2431
1390
                verb, args, body)
2444
1403
            # Recurse just once and we should use the fallback code.
2445
1404
            return self._get_parent_map_rpc(keys)
2446
1405
        response_tuple, response_handler = response
2447
 
        if response_tuple[0] not in [b'ok']:
 
1406
        if response_tuple[0] not in ['ok']:
2448
1407
            response_handler.cancel_read_body()
2449
1408
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2450
 
        if response_tuple[0] == b'ok':
 
1409
        if response_tuple[0] == 'ok':
2451
1410
            coded = bz2.decompress(response_handler.read_body_bytes())
2452
 
            if coded == b'':
 
1411
            if coded == '':
2453
1412
                # no revisions found
2454
1413
                return {}
2455
 
            lines = coded.split(b'\n')
 
1414
            lines = coded.split('\n')
2456
1415
            revision_graph = {}
2457
1416
            for line in lines:
2458
1417
                d = tuple(line.split())
2460
1419
                    revision_graph[d[0]] = d[1:]
2461
1420
                else:
2462
1421
                    # No parents:
2463
 
                    if d[0].startswith(b'missing:'):
 
1422
                    if d[0].startswith('missing:'):
2464
1423
                        revid = d[0][8:]
2465
1424
                        self._unstacked_provider.note_missing_key(revid)
2466
1425
                    else:
2469
1428
                        revision_graph[d[0]] = (NULL_REVISION,)
2470
1429
            return revision_graph
2471
1430
 
 
1431
    @needs_read_lock
2472
1432
    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)
 
1433
        self._ensure_real()
 
1434
        return self._real_repository.get_signature_text(revision_id)
 
1435
 
 
1436
    @needs_read_lock
 
1437
    def get_inventory_xml(self, revision_id):
 
1438
        self._ensure_real()
 
1439
        return self._real_repository.get_inventory_xml(revision_id)
 
1440
 
 
1441
    def deserialise_inventory(self, revision_id, xml):
 
1442
        self._ensure_real()
 
1443
        return self._real_repository.deserialise_inventory(revision_id, xml)
2499
1444
 
2500
1445
    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
 
1446
        self._ensure_real()
 
1447
        return self._real_repository.reconcile(other=other, thorough=thorough)
2528
1448
 
2529
1449
    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
 
 
 
1450
        self._ensure_real()
 
1451
        return self._real_repository.all_revision_ids()
 
1452
 
 
1453
    @needs_read_lock
 
1454
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1455
        self._ensure_real()
 
1456
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1457
            specific_fileids=specific_fileids)
 
1458
 
 
1459
    @needs_read_lock
 
1460
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1461
        self._ensure_real()
 
1462
        return self._real_repository.get_revision_delta(revision_id,
 
1463
            specific_fileids=specific_fileids)
 
1464
 
 
1465
    @needs_read_lock
2565
1466
    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)
 
1467
        self._ensure_real()
 
1468
        return self._real_repository.revision_trees(revision_ids)
2570
1469
 
 
1470
    @needs_read_lock
2571
1471
    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)
 
1472
        self._ensure_real()
 
1473
        return self._real_repository.get_revision_reconcile(revision_id)
2575
1474
 
 
1475
    @needs_read_lock
2576
1476
    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)
 
1477
        self._ensure_real()
 
1478
        return self._real_repository.check(revision_ids=revision_ids,
 
1479
            callback_refs=callback_refs, check_repo=check_repo)
2581
1480
 
2582
1481
    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)
 
1482
        self._ensure_real()
 
1483
        return self._real_repository.copy_content_into(
 
1484
            destination, revision_id=revision_id)
2590
1485
 
2591
1486
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2592
1487
        # get a tarball of the remote repository, and copy from that into the
2593
1488
        # destination
 
1489
        from bzrlib import osutils
2594
1490
        import tarfile
2595
1491
        # TODO: Maybe a progress bar while streaming the tarball?
2596
 
        note(gettext("Copying repository content as tarball..."))
 
1492
        note("Copying repository content as tarball...")
2597
1493
        tar_file = self._get_tarball('bz2')
2598
1494
        if tar_file is None:
2599
1495
            return None
2600
1496
        destination = to_bzrdir.create_repository()
2601
1497
        try:
2602
1498
            tar = tarfile.open('repository', fileobj=tar_file,
2603
 
                               mode='r|bz2')
 
1499
                mode='r|bz2')
2604
1500
            tmpdir = osutils.mkdtemp()
2605
1501
            try:
2606
 
                tar.extractall(tmpdir)
2607
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
1502
                _extract_tar(tar, tmpdir)
 
1503
                tmp_bzrdir = BzrDir.open(tmpdir)
2608
1504
                tmp_repo = tmp_bzrdir.open_repository()
2609
1505
                tmp_repo.copy_content_into(destination, revision_id)
2610
1506
            finally:
2625
1521
        self._ensure_real()
2626
1522
        return self._real_repository.inventories
2627
1523
 
2628
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1524
    @needs_write_lock
 
1525
    def pack(self, hint=None):
2629
1526
        """Compress the data within the repository.
 
1527
 
 
1528
        This is not currently implemented within the smart server.
2630
1529
        """
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)
 
1530
        self._ensure_real()
 
1531
        return self._real_repository.pack(hint=hint)
2648
1532
 
2649
1533
    @property
2650
1534
    def revisions(self):
2651
1535
        """Decorate the real repository for now.
2652
1536
 
 
1537
        In the short term this should become a real object to intercept graph
 
1538
        lookups.
 
1539
 
2653
1540
        In the long term a full blown network facility is needed.
2654
1541
        """
2655
1542
        self._ensure_real()
2657
1544
 
2658
1545
    def set_make_working_trees(self, new_value):
2659
1546
        if new_value:
2660
 
            new_value_str = b"True"
 
1547
            new_value_str = "True"
2661
1548
        else:
2662
 
            new_value_str = b"False"
2663
 
        path = self.controldir._path_for_remote_call(self._client)
 
1549
            new_value_str = "False"
 
1550
        path = self.bzrdir._path_for_remote_call(self._client)
2664
1551
        try:
2665
1552
            response = self._call(
2666
 
                b'Repository.set_make_working_trees', path, new_value_str)
 
1553
                'Repository.set_make_working_trees', path, new_value_str)
2667
1554
        except errors.UnknownSmartMethod:
2668
1555
            self._ensure_real()
2669
1556
            self._real_repository.set_make_working_trees(new_value)
2670
1557
        else:
2671
 
            if response[0] != b'ok':
 
1558
            if response[0] != 'ok':
2672
1559
                raise errors.UnexpectedSmartServerResponse(response)
2673
1560
 
2674
1561
    @property
2681
1568
        self._ensure_real()
2682
1569
        return self._real_repository.signatures
2683
1570
 
 
1571
    @needs_write_lock
2684
1572
    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)
 
1573
        self._ensure_real()
 
1574
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2690
1575
 
2691
1576
    @property
2692
1577
    def texts(self):
2698
1583
        self._ensure_real()
2699
1584
        return self._real_repository.texts
2700
1585
 
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
 
1586
    @needs_read_lock
 
1587
    def get_revisions(self, revision_ids):
 
1588
        self._ensure_real()
 
1589
        return self._real_repository.get_revisions(revision_ids)
2751
1590
 
2752
1591
    def supports_rich_root(self):
2753
1592
        return self._format.rich_root_data
2754
1593
 
 
1594
    def iter_reverse_revision_history(self, revision_id):
 
1595
        self._ensure_real()
 
1596
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
1597
 
2755
1598
    @property
2756
1599
    def _serializer(self):
2757
1600
        return self._format._serializer
2758
1601
 
2759
1602
    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)
 
1603
        self._ensure_real()
 
1604
        return self._real_repository.store_revision_signature(
 
1605
            gpg_strategy, plaintext, revision_id)
2763
1606
 
2764
1607
    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:]]
 
1608
        self._ensure_real()
 
1609
        return self._real_repository.add_signature_text(revision_id, signature)
2784
1610
 
2785
1611
    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)
 
1612
        self._ensure_real()
 
1613
        return self._real_repository.has_signature_for_revision_id(revision_id)
2817
1614
 
2818
1615
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2819
1616
        self._ensure_real()
2820
1617
        return self._real_repository.item_keys_introduced_by(revision_ids,
2821
 
                                                             _files_pb=_files_pb)
 
1618
            _files_pb=_files_pb)
 
1619
 
 
1620
    def revision_graph_can_have_wrong_parents(self):
 
1621
        # The answer depends on the remote repo format.
 
1622
        self._ensure_real()
 
1623
        return self._real_repository.revision_graph_can_have_wrong_parents()
2822
1624
 
2823
1625
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2824
1626
        self._ensure_real()
2833
1635
        providers = [self._unstacked_provider]
2834
1636
        if other is not None:
2835
1637
            providers.insert(0, other)
2836
 
        return graph.StackedParentsProvider(_LazyListJoin(
2837
 
            providers, self._fallback_repositories))
 
1638
        providers.extend(r._make_parents_provider() for r in
 
1639
                         self._fallback_repositories)
 
1640
        return graph.StackedParentsProvider(providers)
2838
1641
 
2839
1642
    def _serialise_search_recipe(self, recipe):
2840
1643
        """Serialise a graph search recipe.
2842
1645
        :param recipe: A search recipe (start, stop, count).
2843
1646
        :return: Serialised bytes.
2844
1647
        """
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))
 
1648
        start_keys = ' '.join(recipe[1])
 
1649
        stop_keys = ' '.join(recipe[2])
 
1650
        count = str(recipe[3])
 
1651
        return '\n'.join((start_keys, stop_keys, count))
2849
1652
 
2850
1653
    def _serialise_search_result(self, search_result):
2851
 
        parts = search_result.get_network_struct()
2852
 
        return b'\n'.join(parts)
 
1654
        if isinstance(search_result, graph.PendingAncestryResult):
 
1655
            parts = ['ancestry-of']
 
1656
            parts.extend(search_result.heads)
 
1657
        else:
 
1658
            recipe = search_result.get_recipe()
 
1659
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1660
        return '\n'.join(parts)
2853
1661
 
2854
1662
    def autopack(self):
2855
 
        path = self.controldir._path_for_remote_call(self._client)
 
1663
        path = self.bzrdir._path_for_remote_call(self._client)
2856
1664
        try:
2857
 
            response = self._call(b'PackRepository.autopack', path)
 
1665
            response = self._call('PackRepository.autopack', path)
2858
1666
        except errors.UnknownSmartMethod:
2859
1667
            self._ensure_real()
2860
1668
            self._real_repository._pack_collection.autopack()
2861
1669
            return
2862
1670
        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):
 
1671
        if response[0] != 'ok':
 
1672
            raise errors.UnexpectedSmartServerResponse(response)
 
1673
 
 
1674
 
 
1675
class RemoteStreamSink(repository.StreamSink):
2905
1676
 
2906
1677
    def _insert_real(self, stream, src_format, resume_tokens):
2907
1678
        self.target_repo._ensure_real()
2911
1682
            self.target_repo.autopack()
2912
1683
        return result
2913
1684
 
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
1685
    def insert_stream(self, stream, src_format, resume_tokens):
2926
1686
        target = self.target_repo
2927
1687
        target._unstacked_provider.missing_keys.clear()
2928
 
        candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
 
1688
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2929
1689
        if target._lock_token:
2930
 
            candidate_calls.append(
2931
 
                (b'Repository.insert_stream_locked', (1, 14)))
2932
 
            lock_args = (target._lock_token or b'',)
 
1690
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1691
            lock_args = (target._lock_token or '',)
2933
1692
        else:
2934
 
            candidate_calls.append((b'Repository.insert_stream', (1, 13)))
 
1693
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
2935
1694
            lock_args = ()
2936
1695
        client = target._client
2937
1696
        medium = client._medium
2938
 
        path = target.controldir._path_for_remote_call(client)
 
1697
        path = target.bzrdir._path_for_remote_call(client)
2939
1698
        # Probe for the verb to use with an empty stream before sending the
2940
1699
        # real stream to it.  We do this both to avoid the risk of sending a
2941
1700
        # large request that is then rejected, and because we don't want to
2952
1711
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2953
1712
            try:
2954
1713
                response = client.call_with_body_stream(
2955
 
                    (verb, path, b'') + lock_args, byte_stream)
 
1714
                    (verb, path, '') + lock_args, byte_stream)
2956
1715
            except errors.UnknownSmartMethod:
2957
1716
                medium._remember_remote_is_before(required_version)
2958
1717
            else:
2971
1730
            stream = self._stop_stream_if_inventory_delta(stream)
2972
1731
        byte_stream = smart_repo._stream_to_byte_stream(
2973
1732
            stream, src_format)
2974
 
        resume_tokens = b' '.join([token.encode('utf-8')
2975
 
                                   for token in resume_tokens])
 
1733
        resume_tokens = ' '.join(resume_tokens)
2976
1734
        response = client.call_with_body_stream(
2977
1735
            (verb, path, resume_tokens) + lock_args, byte_stream)
2978
 
        if response[0][0] not in (b'ok', b'missing-basis'):
 
1736
        if response[0][0] not in ('ok', 'missing-basis'):
2979
1737
            raise errors.UnexpectedSmartServerResponse(response)
2980
1738
        if self._last_substream is not None:
2981
1739
            # The stream included an inventory-delta record, but the remote
2982
1740
            # side isn't new enough to support them.  So we need to send the
2983
1741
            # rest of the stream via VFS.
2984
 
            self.target_repo.refresh_data()
2985
1742
            return self._resume_stream_with_vfs(response, src_format)
2986
 
        if response[0][0] == b'missing-basis':
 
1743
        if response[0][0] == 'missing-basis':
2987
1744
            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)
 
1745
            resume_tokens = tokens
 
1746
            return resume_tokens, set(missing_keys)
2990
1747
        else:
2991
1748
            self.target_repo.refresh_data()
2992
1749
            return [], set()
2995
1752
        """Resume sending a stream via VFS, first resending the record and
2996
1753
        substream that couldn't be sent via an insert_stream verb.
2997
1754
        """
2998
 
        if response[0][0] == b'missing-basis':
 
1755
        if response[0][0] == 'missing-basis':
2999
1756
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3000
 
            tokens = [token.decode('utf-8') for token in tokens]
3001
1757
            # Ignore missing_keys, we haven't finished inserting yet
3002
1758
        else:
3003
1759
            tokens = []
3004
 
 
3005
1760
        def resume_substream():
3006
1761
            # Yield the substream that was interrupted.
3007
1762
            for record in self._last_substream:
3008
1763
                yield record
3009
1764
            self._last_substream = None
3010
 
 
3011
1765
        def resume_stream():
3012
1766
            # Finish sending the interrupted substream
3013
1767
            yield ('inventory-deltas', resume_substream())
3024
1778
        self._last_substream and self._last_stream so that the stream can be
3025
1779
        resumed by _resume_stream_with_vfs.
3026
1780
        """
3027
 
 
 
1781
                    
3028
1782
        stream_iter = iter(stream)
3029
1783
        for substream_kind, substream in stream_iter:
3030
1784
            if substream_kind == 'inventory-deltas':
3033
1787
                return
3034
1788
            else:
3035
1789
                yield substream_kind, substream
3036
 
 
3037
 
 
3038
 
class RemoteStreamSource(vf_repository.StreamSource):
 
1790
            
 
1791
 
 
1792
class RemoteStreamSource(repository.StreamSource):
3039
1793
    """Stream data from a remote server."""
3040
1794
 
3041
1795
    def get_stream(self, search):
3042
 
        if (self.from_repository._fallback_repositories
3043
 
                and self.to_format._fetch_order == 'topological'):
 
1796
        if (self.from_repository._fallback_repositories and
 
1797
            self.to_format._fetch_order == 'topological'):
3044
1798
            return self._real_stream(self.from_repository, search)
3045
1799
        sources = []
3046
1800
        seen = set()
3054
1808
            sources.append(repo)
3055
1809
        return self.missing_parents_chain(search, sources)
3056
1810
 
3057
 
    def _get_real_stream_for_missing_keys(self, missing_keys):
 
1811
    def get_stream_for_missing_keys(self, missing_keys):
3058
1812
        self.from_repository._ensure_real()
3059
1813
        real_repo = self.from_repository._real_repository
3060
1814
        real_source = real_repo._get_source(self.to_format)
3061
1815
        return real_source.get_stream_for_missing_keys(missing_keys)
3062
1816
 
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
1817
    def _real_stream(self, repo, search):
3091
1818
        """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
 
1819
        
 
1820
        This never called RemoteStreamSource.get_stream, and is a heler
 
1821
        for RemoteStreamSource._get_stream to allow getting a stream 
3095
1822
        reliably whether fallback back because of old servers or trying
3096
1823
        to stream from a non-RemoteRepository (which the stacked support
3097
1824
        code will do).
3122
1849
            return self._real_stream(repo, search)
3123
1850
        client = repo._client
3124
1851
        medium = client._medium
3125
 
        path = repo.controldir._path_for_remote_call(client)
 
1852
        path = repo.bzrdir._path_for_remote_call(client)
3126
1853
        search_bytes = repo._serialise_search_result(search)
3127
1854
        args = (path, self.to_format.network_name())
3128
1855
        candidate_verbs = [
3129
 
            (b'Repository.get_stream_1.19', (1, 19)),
3130
 
            (b'Repository.get_stream', (1, 13))]
3131
 
 
 
1856
            ('Repository.get_stream_1.19', (1, 19)),
 
1857
            ('Repository.get_stream', (1, 13))]
3132
1858
        found_verb = False
3133
1859
        for verb, version in candidate_verbs:
3134
1860
            if medium._is_remote_before(version):
3138
1864
                    verb, args, search_bytes)
3139
1865
            except errors.UnknownSmartMethod:
3140
1866
                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
1867
            else:
3153
1868
                response_tuple, response_handler = response
3154
1869
                found_verb = True
3155
1870
                break
3156
1871
        if not found_verb:
3157
1872
            return self._real_stream(repo, search)
3158
 
        if response_tuple[0] != b'ok':
 
1873
        if response_tuple[0] != 'ok':
3159
1874
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3160
1875
        byte_stream = response_handler.read_streamed_body()
3161
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3162
 
                                                               self._record_counter)
 
1876
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
3163
1877
        if src_format.network_name() != repo._format.network_name():
3164
1878
            raise AssertionError(
3165
1879
                "Mismatched RemoteRepository and stream src %r, %r" % (
3166
 
                    src_format.network_name(), repo._format.network_name()))
 
1880
                src_format.network_name(), repo._format.network_name()))
3167
1881
        return stream
3168
1882
 
3169
1883
    def missing_parents_chain(self, search, sources):
3172
1886
        :param search: The overall search to satisfy with streams.
3173
1887
        :param sources: A list of Repository objects to query.
3174
1888
        """
3175
 
        self.from_serialiser = self.from_repository._format._serializer
 
1889
        self.serialiser = self.to_format._serializer
3176
1890
        self.seen_revs = set()
3177
1891
        self.referenced_revs = set()
3178
1892
        # If there are heads in the search, or the key count is > 0, we are not
3195
1909
    def missing_parents_rev_handler(self, substream):
3196
1910
        for content in substream:
3197
1911
            revision_bytes = content.get_bytes_as('fulltext')
3198
 
            revision = self.from_serialiser.read_revision_from_string(
3199
 
                revision_bytes)
 
1912
            revision = self.serialiser.read_revision_from_string(revision_bytes)
3200
1913
            self.seen_revs.add(content.key[-1])
3201
1914
            self.referenced_revs.update(revision.parent_ids)
3202
1915
            yield content
3209
1922
    """
3210
1923
 
3211
1924
    def __init__(self, bzrdir, _client):
3212
 
        self.controldir = bzrdir
 
1925
        self.bzrdir = bzrdir
3213
1926
        self._client = _client
3214
1927
        self._need_find_modes = True
3215
1928
        LockableFiles.__init__(
3226
1939
 
3227
1940
    def __init__(self, network_name=None):
3228
1941
        super(RemoteBranchFormat, self).__init__()
3229
 
        self._matchingcontroldir = RemoteBzrDirFormat()
3230
 
        self._matchingcontroldir.set_branch_format(self)
 
1942
        self._matchingbzrdir = RemoteBzrDirFormat()
 
1943
        self._matchingbzrdir.set_branch_format(self)
3231
1944
        self._custom_format = None
3232
1945
        self._network_name = network_name
3233
1946
 
3234
1947
    def __eq__(self, other):
3235
 
        return (isinstance(other, RemoteBranchFormat)
3236
 
                and self.__dict__ == other.__dict__)
 
1948
        return (isinstance(other, RemoteBranchFormat) and
 
1949
            self.__dict__ == other.__dict__)
3237
1950
 
3238
1951
    def _ensure_real(self):
3239
1952
        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)
 
1953
            self._custom_format = branch.network_format_registry.get(
 
1954
                self._network_name)
3246
1955
 
3247
1956
    def get_format_description(self):
3248
 
        self._ensure_real()
3249
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
1957
        return 'Remote BZR Branch'
3250
1958
 
3251
1959
    def network_name(self):
3252
1960
        return self._network_name
3253
1961
 
3254
 
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
3255
 
        return a_controldir.open_branch(name=name,
3256
 
                                        ignore_fallbacks=ignore_fallbacks)
 
1962
    def open(self, a_bzrdir, ignore_fallbacks=False):
 
1963
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
3257
1964
 
3258
 
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3259
 
                        repository=None):
 
1965
    def _vfs_initialize(self, a_bzrdir):
3260
1966
        # Initialisation when using a local bzrdir object, or a non-vfs init
3261
1967
        # method is not available on the server.
3262
1968
        # self._custom_format is always set - the start of initialize ensures
3263
1969
        # 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)
 
1970
        if isinstance(a_bzrdir, RemoteBzrDir):
 
1971
            a_bzrdir._ensure_real()
 
1972
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
3269
1973
        else:
3270
1974
            # 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)
 
1975
            result = self._custom_format.initialize(a_bzrdir)
 
1976
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
1977
            not isinstance(result, RemoteBranch)):
 
1978
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
3278
1979
        return result
3279
1980
 
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()
 
1981
    def initialize(self, a_bzrdir):
3284
1982
        # 1) get the network name to use.
3285
1983
        if self._custom_format:
3286
1984
            network_name = self._custom_format.network_name()
3287
1985
        else:
3288
 
            # Select the current breezy default and ask for that.
3289
 
            reference_bzrdir_format = controldir.format_registry.get(
3290
 
                'default')()
 
1986
            # Select the current bzrlib default and ask for that.
 
1987
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
3291
1988
            reference_format = reference_bzrdir_format.get_branch_format()
3292
1989
            self._custom_format = reference_format
3293
1990
            network_name = reference_format.network_name()
3294
1991
        # 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
 
1992
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
1993
            return self._vfs_initialize(a_bzrdir)
 
1994
        medium = a_bzrdir._client._medium
3300
1995
        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)
 
1996
            return self._vfs_initialize(a_bzrdir)
3304
1997
        # Creating on a remote bzr dir.
3305
1998
        # 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'
 
1999
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2000
        verb = 'BzrDir.create_branch'
3311
2001
        try:
3312
 
            response = a_controldir._call(verb, path, network_name)
 
2002
            response = a_bzrdir._call(verb, path, network_name)
3313
2003
        except errors.UnknownSmartMethod:
3314
2004
            # Fallback - use vfs methods
3315
2005
            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':
 
2006
            return self._vfs_initialize(a_bzrdir)
 
2007
        if response[0] != 'ok':
3320
2008
            raise errors.UnexpectedSmartServerResponse(response)
3321
2009
        # Turn the response into a RemoteRepository object.
3322
2010
        format = RemoteBranchFormat(network_name=response[1])
3323
2011
        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
 
2012
        if response[2] == '':
 
2013
            repo_bzrdir = a_bzrdir
3335
2014
        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)
 
2015
            repo_bzrdir = RemoteBzrDir(
 
2016
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2017
                a_bzrdir._client)
 
2018
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2019
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2020
            format=format, setup_stacking=False)
3348
2021
        # XXX: We know this is a new branch, so it must have revno 0, revid
3349
2022
        # NULL_REVISION. Creating the branch locked would make this be unable
3350
2023
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3369
2042
        self._ensure_real()
3370
2043
        return self._custom_format.supports_set_append_revisions_only()
3371
2044
 
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):
 
2045
 
 
2046
class RemoteBranch(branch.Branch, _RpcHelper):
3443
2047
    """Branch stored on a server accessed by HPSS RPC.
3444
2048
 
3445
2049
    At the moment most operations are mapped down to simple file operations.
3446
2050
    """
3447
2051
 
3448
2052
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3449
 
                 _client=None, format=None, setup_stacking=True, name=None,
3450
 
                 possible_transports=None):
 
2053
        _client=None, format=None, setup_stacking=True):
3451
2054
        """Create a RemoteBranch instance.
3452
2055
 
3453
2056
        :param real_branch: An optional local implementation of the branch
3459
2062
        :param setup_stacking: If True make an RPC call to determine the
3460
2063
            stacked (or not) status of the branch. If False assume the branch
3461
2064
            is not stacked.
3462
 
        :param name: Colocated branch name
3463
2065
        """
3464
2066
        # We intentionally don't call the parent class's __init__, because it
3465
2067
        # will try to assign to self.tags, which is a property in this subclass.
3466
2068
        # And the parent's __init__ doesn't do much anyway.
3467
 
        self.controldir = remote_bzrdir
3468
 
        self.name = name
 
2069
        self.bzrdir = remote_bzrdir
3469
2070
        if _client is not None:
3470
2071
            self._client = _client
3471
2072
        else:
3483
2084
            self._real_branch.repository = self.repository
3484
2085
        else:
3485
2086
            self._real_branch = None
3486
 
        # Fill out expected attributes of branch for breezy API users.
 
2087
        # Fill out expected attributes of branch for bzrlib API users.
3487
2088
        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
 
2089
        self.base = self.bzrdir.root_transport.base
3491
2090
        self._control_files = None
3492
2091
        self._lock_mode = None
3493
2092
        self._lock_token = None
3494
2093
        self._repo_lock_token = None
3495
2094
        self._lock_count = 0
3496
2095
        self._leave_lock = False
3497
 
        self.conf_store = None
3498
2096
        # Setup a format: note that we cannot call _ensure_real until all the
3499
2097
        # attributes above are set: This code cannot be moved higher up in this
3500
2098
        # function.
3520
2118
            hook(self)
3521
2119
        self._is_stacked = False
3522
2120
        if setup_stacking:
3523
 
            self._setup_stacking(possible_transports)
 
2121
            self._setup_stacking()
3524
2122
 
3525
 
    def _setup_stacking(self, possible_transports):
 
2123
    def _setup_stacking(self):
3526
2124
        # configure stacking into the remote repository, by reading it from
3527
2125
        # the vfs branch.
3528
2126
        try:
3529
2127
            fallback_url = self.get_stacked_on_url()
3530
 
        except (errors.NotStacked, branch.UnstackableBranchFormat,
3531
 
                errors.UnstackableRepositoryFormat) as e:
 
2128
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2129
            errors.UnstackableRepositoryFormat), e:
3532
2130
            return
3533
2131
        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)
 
2132
        self._activate_fallback_location(fallback_url)
3541
2133
 
3542
2134
    def _get_config(self):
3543
2135
        return RemoteBranchConfig(self)
3544
2136
 
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
2137
    def _get_real_transport(self):
3559
2138
        # if we try vfs access, return the real branch's vfs transport
3560
2139
        self._ensure_real()
3575
2154
        if self._real_branch is None:
3576
2155
            if not vfs.vfs_enabled():
3577
2156
                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
 
2157
                    'to use vfs implementation')
 
2158
            self.bzrdir._ensure_real()
 
2159
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
2160
                ignore_fallbacks=self._real_ignore_fallbacks)
3586
2161
            if self.repository._real_repository is None:
3587
2162
                # Give the remote repository the matching real repo.
3588
2163
                real_repo = self._real_branch.repository
3603
2178
 
3604
2179
    def _clear_cached_state(self):
3605
2180
        super(RemoteBranch, self)._clear_cached_state()
3606
 
        self._tags_bytes = None
3607
2181
        if self._real_branch is not None:
3608
2182
            self._real_branch._clear_cached_state()
3609
2183
 
3625
2199
        # because it triggers an _ensure_real that we otherwise might not need.
3626
2200
        if self._control_files is None:
3627
2201
            self._control_files = RemoteBranchLockableFiles(
3628
 
                self.controldir, self._client)
 
2202
                self.bzrdir, self._client)
3629
2203
        return self._control_files
3630
2204
 
 
2205
    def _get_checkout_format(self):
 
2206
        self._ensure_real()
 
2207
        return self._real_branch._get_checkout_format()
 
2208
 
3631
2209
    def get_physical_lock_status(self):
3632
2210
        """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')
 
2211
        # should be an API call to the server, as branches must be lockable.
 
2212
        self._ensure_real()
 
2213
        return self._real_branch.get_physical_lock_status()
3642
2214
 
3643
2215
    def get_stacked_on_url(self):
3644
2216
        """Get the URL this branch is stacked against.
3652
2224
        try:
3653
2225
            # there may not be a repository yet, so we can't use
3654
2226
            # 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:
 
2227
            response = self._client.call('Branch.get_stacked_on_url',
 
2228
                self._remote_path())
 
2229
        except errors.ErrorFromSmartServer, err:
3658
2230
            # there may not be a repository yet, so we can't call through
3659
2231
            # its _translate_error
3660
2232
            _translate_error(err, branch=self)
3661
 
        except errors.UnknownSmartMethod as err:
 
2233
        except errors.UnknownSmartMethod, err:
3662
2234
            self._ensure_real()
3663
2235
            return self._real_branch.get_stacked_on_url()
3664
 
        if response[0] != b'ok':
 
2236
        if response[0] != 'ok':
3665
2237
            raise errors.UnexpectedSmartServerResponse(response)
3666
 
        return response[1].decode('utf-8')
 
2238
        return response[1]
3667
2239
 
3668
2240
    def set_stacked_on_url(self, url):
3669
2241
        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
2242
        if not url:
3678
2243
            self._is_stacked = False
3679
2244
        else:
3680
2245
            self._is_stacked = True
3681
 
 
 
2246
        
3682
2247
    def _vfs_get_tags_bytes(self):
3683
2248
        self._ensure_real()
3684
2249
        return self._real_branch._get_tags_bytes()
3685
2250
 
3686
2251
    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
2252
        medium = self._client._medium
3694
2253
        if medium._is_remote_before((1, 13)):
3695
2254
            return self._vfs_get_tags_bytes()
3696
2255
        try:
3697
 
            response = self._call(
3698
 
                b'Branch.get_tags_bytes', self._remote_path())
 
2256
            response = self._call('Branch.get_tags_bytes', self._remote_path())
3699
2257
        except errors.UnknownSmartMethod:
3700
2258
            medium._remember_remote_is_before((1, 13))
3701
2259
            return self._vfs_get_tags_bytes()
3706
2264
        return self._real_branch._set_tags_bytes(bytes)
3707
2265
 
3708
2266
    def _set_tags_bytes(self, bytes):
3709
 
        if self.is_locked():
3710
 
            self._tags_bytes = bytes
3711
2267
        medium = self._client._medium
3712
2268
        if medium._is_remote_before((1, 18)):
3713
2269
            self._vfs_set_tags_bytes(bytes)
3714
 
            return
3715
2270
        try:
3716
2271
            args = (
3717
2272
                self._remote_path(), self._lock_token, self._repo_lock_token)
3718
2273
            response = self._call_with_body_bytes(
3719
 
                b'Branch.set_tags_bytes', args, bytes)
 
2274
                'Branch.set_tags_bytes', args, bytes)
3720
2275
        except errors.UnknownSmartMethod:
3721
2276
            medium._remember_remote_is_before((1, 18))
3722
2277
            self._vfs_set_tags_bytes(bytes)
3723
2278
 
3724
2279
    def lock_read(self):
3725
 
        """Lock the branch for read operations.
3726
 
 
3727
 
        :return: A breezy.lock.LogicalLockResult.
3728
 
        """
3729
2280
        self.repository.lock_read()
3730
2281
        if not self._lock_mode:
3731
 
            self._note_lock('r')
3732
2282
            self._lock_mode = 'r'
3733
2283
            self._lock_count = 1
3734
2284
            if self._real_branch is not None:
3735
2285
                self._real_branch.lock_read()
3736
2286
        else:
3737
2287
            self._lock_count += 1
3738
 
        return lock.LogicalLockResult(self.unlock)
3739
2288
 
3740
2289
    def _remote_lock_write(self, token):
3741
2290
        if token is None:
3742
 
            branch_token = repo_token = b''
 
2291
            branch_token = repo_token = ''
3743
2292
        else:
3744
2293
            branch_token = token
3745
 
            repo_token = self.repository.lock_write().repository_token
 
2294
            repo_token = self.repository.lock_write()
3746
2295
            self.repository.unlock()
3747
2296
        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':
 
2297
        response = self._call(
 
2298
            'Branch.lock_write', self._remote_path(), branch_token,
 
2299
            repo_token or '', **err_context)
 
2300
        if response[0] != 'ok':
3759
2301
            raise errors.UnexpectedSmartServerResponse(response)
3760
2302
        ok, branch_token, repo_token = response
3761
2303
        return branch_token, repo_token
3762
2304
 
3763
2305
    def lock_write(self, token=None):
3764
2306
        if not self._lock_mode:
3765
 
            self._note_lock('w')
3766
2307
            # Lock the branch and repo in one remote call.
3767
2308
            remote_tokens = self._remote_lock_write(token)
3768
2309
            self._lock_token, self._repo_lock_token = remote_tokens
3769
2310
            if not self._lock_token:
3770
 
                raise SmartProtocolError(
3771
 
                    'Remote server did not return a token!')
 
2311
                raise SmartProtocolError('Remote server did not return a token!')
3772
2312
            # Tell the self.repository object that it is locked.
3773
2313
            self.repository.lock_write(
3774
2314
                self._repo_lock_token, _skip_rpc=True)
3782
2322
            self._lock_mode = 'w'
3783
2323
            self._lock_count = 1
3784
2324
        elif self._lock_mode == 'r':
3785
 
            raise errors.ReadOnlyError(self)
 
2325
            raise errors.ReadOnlyTransaction
3786
2326
        else:
3787
2327
            if token is not None:
3788
2328
                # A token was given to lock_write, and we're relocking, so
3793
2333
            self._lock_count += 1
3794
2334
            # Re-lock the repository too.
3795
2335
            self.repository.lock_write(self._repo_lock_token)
3796
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
2336
        return self._lock_token or None
3797
2337
 
3798
2338
    def _unlock(self, branch_token, repo_token):
3799
2339
        err_context = {'token': str((branch_token, repo_token))}
3800
2340
        response = self._call(
3801
 
            b'Branch.unlock', self._remote_path(), branch_token,
3802
 
            repo_token or b'', **err_context)
3803
 
        if response == (b'ok',):
 
2341
            'Branch.unlock', self._remote_path(), branch_token,
 
2342
            repo_token or '', **err_context)
 
2343
        if response == ('ok',):
3804
2344
            return
3805
2345
        raise errors.UnexpectedSmartServerResponse(response)
3806
2346
 
3807
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
3808
2347
    def unlock(self):
3809
2348
        try:
3810
2349
            self._lock_count -= 1
3811
2350
            if not self._lock_count:
3812
 
                if self.conf_store is not None:
3813
 
                    self.conf_store.save_changes()
3814
2351
                self._clear_cached_state()
3815
2352
                mode = self._lock_mode
3816
2353
                self._lock_mode = None
3817
2354
                if self._real_branch is not None:
3818
 
                    if (not self._leave_lock and mode == 'w'
3819
 
                            and self._repo_lock_token):
 
2355
                    if (not self._leave_lock and mode == 'w' and
 
2356
                        self._repo_lock_token):
3820
2357
                        # If this RemoteBranch will remove the physical lock
3821
2358
                        # for the repository, make sure the _real_branch
3822
2359
                        # doesn't do it first.  (Because the _real_branch's
3839
2376
            self.repository.unlock()
3840
2377
 
3841
2378
    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)
 
2379
        self._ensure_real()
 
2380
        return self._real_branch.break_lock()
3850
2381
 
3851
2382
    def leave_lock_in_place(self):
3852
2383
        if not self._lock_token:
3861
2392
    def get_rev_id(self, revno, history=None):
3862
2393
        if revno == 0:
3863
2394
            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)
 
2395
        last_revision_info = self.last_revision_info()
 
2396
        ok, result = self.repository.get_rev_id_for_revno(
 
2397
            revno, last_revision_info)
 
2398
        if ok:
 
2399
            return result
 
2400
        missing_parent = result[1]
 
2401
        # Either the revision named by the server is missing, or its parent
 
2402
        # is.  Call get_parent_map to determine which, so that we report a
 
2403
        # useful error.
 
2404
        parent_map = self.repository.get_parent_map([missing_parent])
 
2405
        if missing_parent in parent_map:
 
2406
            missing_parent = parent_map[missing_parent]
 
2407
        raise errors.RevisionNotPresent(missing_parent, self.repository)
3881
2408
 
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,))
 
2409
    def _last_revision_info(self):
 
2410
        response = self._call('Branch.last_revision_info', self._remote_path())
 
2411
        if response[0] != 'ok':
 
2412
            raise SmartProtocolError('unexpected response code %s' % (response,))
3888
2413
        revno = int(response[1])
3889
2414
        last_revision = response[2]
3890
2415
        return (revno, last_revision)
3895
2420
            self._ensure_real()
3896
2421
            return self._real_branch._gen_revision_history()
3897
2422
        response_tuple, response_handler = self._call_expecting_body(
3898
 
            b'Branch.revision_history', self._remote_path())
3899
 
        if response_tuple[0] != b'ok':
 
2423
            'Branch.revision_history', self._remote_path())
 
2424
        if response_tuple[0] != 'ok':
3900
2425
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3901
 
        result = response_handler.read_body_bytes().split(b'\x00')
 
2426
        result = response_handler.read_body_bytes().split('\x00')
3902
2427
        if result == ['']:
3903
2428
            return []
3904
2429
        return result
3905
2430
 
3906
2431
    def _remote_path(self):
3907
 
        return self.controldir._path_for_remote_call(self._client)
 
2432
        return self.bzrdir._path_for_remote_call(self._client)
3908
2433
 
3909
2434
    def _set_last_revision_descendant(self, revision_id, other_branch,
3910
 
                                      allow_diverged=False, allow_overwrite_descendant=False):
 
2435
            allow_diverged=False, allow_overwrite_descendant=False):
3911
2436
        # This performs additional work to meet the hook contract; while its
3912
2437
        # undesirable, we have to synthesise the revno to call the hook, and
3913
2438
        # not calling the hook is worse as it means changes can't be prevented.
3918
2443
        history = self._lefthand_history(revision_id)
3919
2444
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3920
2445
        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)
 
2446
        response = self._call('Branch.set_last_revision_ex',
 
2447
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2448
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
2449
            **err_context)
3926
2450
        self._clear_cached_state()
3927
 
        if len(response) != 3 and response[0] != b'ok':
 
2451
        if len(response) != 3 and response[0] != 'ok':
3928
2452
            raise errors.UnexpectedSmartServerResponse(response)
3929
2453
        new_revno, new_revision_id = response[1:]
3930
2454
        self._last_revision_info_cache = new_revno, new_revision_id
3944
2468
        history = self._lefthand_history(revision_id)
3945
2469
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3946
2470
        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',):
 
2471
        response = self._call('Branch.set_last_revision',
 
2472
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2473
            revision_id)
 
2474
        if response != ('ok',):
3951
2475
            raise errors.UnexpectedSmartServerResponse(response)
3952
2476
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3953
2477
 
 
2478
    @needs_write_lock
 
2479
    def set_revision_history(self, rev_history):
 
2480
        # Send just the tip revision of the history; the server will generate
 
2481
        # the full history from that.  If the revision doesn't exist in this
 
2482
        # branch, NoSuchRevision will be raised.
 
2483
        if rev_history == []:
 
2484
            rev_id = 'null:'
 
2485
        else:
 
2486
            rev_id = rev_history[-1]
 
2487
        self._set_last_revision(rev_id)
 
2488
        for hook in branch.Branch.hooks['set_rh']:
 
2489
            hook(self, rev_history)
 
2490
        self._cache_revision_history(rev_history)
 
2491
 
3954
2492
    def _get_parent_location(self):
3955
2493
        medium = self._client._medium
3956
2494
        if medium._is_remote_before((1, 13)):
3957
2495
            return self._vfs_get_parent_location()
3958
2496
        try:
3959
 
            response = self._call(b'Branch.get_parent', self._remote_path())
 
2497
            response = self._call('Branch.get_parent', self._remote_path())
3960
2498
        except errors.UnknownSmartMethod:
3961
2499
            medium._remember_remote_is_before((1, 13))
3962
2500
            return self._vfs_get_parent_location()
3963
2501
        if len(response) != 1:
3964
2502
            raise errors.UnexpectedSmartServerResponse(response)
3965
2503
        parent_location = response[0]
3966
 
        if parent_location == b'':
 
2504
        if parent_location == '':
3967
2505
            return None
3968
 
        return parent_location.decode('utf-8')
 
2506
        return parent_location
3969
2507
 
3970
2508
    def _vfs_get_parent_location(self):
3971
2509
        self._ensure_real()
3976
2514
        if medium._is_remote_before((1, 15)):
3977
2515
            return self._vfs_set_parent_location(url)
3978
2516
        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)
 
2517
            call_url = url or ''
 
2518
            if type(call_url) is not str:
 
2519
                raise AssertionError('url must be a str or None (%s)' % url)
 
2520
            response = self._call('Branch.set_parent_location',
 
2521
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2522
                call_url)
3985
2523
        except errors.UnknownSmartMethod:
3986
2524
            medium._remember_remote_is_before((1, 15))
3987
2525
            return self._vfs_set_parent_location(url)
3992
2530
        self._ensure_real()
3993
2531
        return self._real_branch._set_parent_location(url)
3994
2532
 
 
2533
    @needs_write_lock
3995
2534
    def pull(self, source, overwrite=False, stop_revision=None,
3996
2535
             **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
 
2536
        self._clear_cached_state_of_remote_branch_only()
 
2537
        self._ensure_real()
 
2538
        return self._real_branch.pull(
 
2539
            source, overwrite=overwrite, stop_revision=stop_revision,
 
2540
            _override_hook_target=self, **kwargs)
 
2541
 
 
2542
    @needs_read_lock
 
2543
    def push(self, target, overwrite=False, stop_revision=None):
 
2544
        self._ensure_real()
 
2545
        return self._real_branch.push(
 
2546
            target, overwrite=overwrite, stop_revision=stop_revision,
 
2547
            _override_hook_source_branch=self)
4013
2548
 
4014
2549
    def is_locked(self):
4015
2550
        return self._lock_count >= 1
4016
2551
 
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
 
 
 
2552
    @needs_read_lock
4042
2553
    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
 
 
 
2554
        self._ensure_real()
 
2555
        return self._real_branch.revision_id_to_revno(revision_id)
 
2556
 
 
2557
    @needs_write_lock
4061
2558
    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)
 
2559
        # XXX: These should be returned by the set_last_revision_info verb
 
2560
        old_revno, old_revid = self.last_revision_info()
 
2561
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2562
        revision_id = ensure_null(revision_id)
 
2563
        try:
 
2564
            response = self._call('Branch.set_last_revision_info',
 
2565
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2566
                str(revno), revision_id)
 
2567
        except errors.UnknownSmartMethod:
 
2568
            self._ensure_real()
 
2569
            self._clear_cached_state_of_remote_branch_only()
 
2570
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2571
            self._last_revision_info_cache = revno, revision_id
 
2572
            return
 
2573
        if response == ('ok',):
 
2574
            self._clear_cached_state()
 
2575
            self._last_revision_info_cache = revno, revision_id
 
2576
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2577
            # Update the _real_branch's cache too.
 
2578
            if self._real_branch is not None:
 
2579
                cache = self._last_revision_info_cache
 
2580
                self._real_branch._last_revision_info_cache = cache
 
2581
        else:
 
2582
            raise errors.UnexpectedSmartServerResponse(response)
4089
2583
 
 
2584
    @needs_write_lock
4090
2585
    def generate_revision_history(self, revision_id, last_rev=None,
4091
2586
                                  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)
 
2587
        medium = self._client._medium
 
2588
        if not medium._is_remote_before((1, 6)):
 
2589
            # Use a smart method for 1.6 and above servers
 
2590
            try:
 
2591
                self._set_last_revision_descendant(revision_id, other_branch,
 
2592
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2593
                return
 
2594
            except errors.UnknownSmartMethod:
 
2595
                medium._remember_remote_is_before((1, 6))
 
2596
        self._clear_cached_state_of_remote_branch_only()
 
2597
        self.set_revision_history(self._lefthand_history(revision_id,
 
2598
            last_rev=last_rev,other_branch=other_branch))
4116
2599
 
4117
2600
    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)
 
2601
        self._ensure_real()
 
2602
        return self._real_branch.set_push_location(location)
4208
2603
 
4209
2604
 
4210
2605
class RemoteConfig(object):
4212
2607
 
4213
2608
    It is a low-level object that considers config data to be name/value pairs
4214
2609
    that may be associated with a section. Assigning meaning to the these
4215
 
    values is done at higher levels like breezy.config.TreeConfig.
 
2610
    values is done at higher levels like bzrlib.config.TreeConfig.
4216
2611
    """
4217
2612
 
4218
2613
    def get_option(self, name, section=None, default=None):
4225
2620
        """
4226
2621
        try:
4227
2622
            configobj = self._get_configobj()
4228
 
            section_obj = None
4229
2623
            if section is None:
4230
2624
                section_obj = configobj
4231
2625
            else:
4232
2626
                try:
4233
2627
                    section_obj = configobj[section]
4234
2628
                except KeyError:
4235
 
                    pass
4236
 
            if section_obj is None:
4237
 
                value = default
4238
 
            else:
4239
 
                value = section_obj.get(name, default)
 
2629
                    return default
 
2630
            return section_obj.get(name, default)
4240
2631
        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
 
2632
            return self._vfs_get_option(name, section, default)
4245
2633
 
4246
2634
    def _response_to_configobj(self, response):
4247
 
        if len(response[0]) and response[0][0] != b'ok':
 
2635
        if len(response[0]) and response[0][0] != 'ok':
4248
2636
            raise errors.UnexpectedSmartServerResponse(response)
4249
2637
        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
 
2638
        return config.ConfigObj(lines, encoding='utf-8')
4254
2639
 
4255
2640
 
4256
2641
class RemoteBranchConfig(RemoteConfig):
4262
2647
    def _get_configobj(self):
4263
2648
        path = self._branch._remote_path()
4264
2649
        response = self._branch._client.call_expecting_body(
4265
 
            b'Branch.get_config_file', path)
 
2650
            'Branch.get_config_file', path)
4266
2651
        return self._response_to_configobj(response)
4267
2652
 
4268
2653
    def set_option(self, value, name, section=None):
4275
2660
        medium = self._branch._client._medium
4276
2661
        if medium._is_remote_before((1, 14)):
4277
2662
            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
2663
        try:
4293
2664
            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'))
 
2665
            response = self._branch._client.call('Branch.set_config_option',
 
2666
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2667
                value.encode('utf8'), name, section or '')
4298
2668
        except errors.UnknownSmartMethod:
4299
 
            medium = self._branch._client._medium
4300
2669
            medium._remember_remote_is_before((1, 14))
4301
2670
            return self._vfs_set_option(value, name, section)
4302
2671
        if response != ():
4303
2672
            raise errors.UnexpectedSmartServerResponse(response)
4304
2673
 
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
2674
    def _real_object(self):
4331
2675
        self._branch._ensure_real()
4332
2676
        return self._branch._real_branch
4344
2688
 
4345
2689
    def _get_configobj(self):
4346
2690
        medium = self._bzrdir._client._medium
4347
 
        verb = b'BzrDir.get_config_file'
 
2691
        verb = 'BzrDir.get_config_file'
4348
2692
        if medium._is_remote_before((1, 15)):
4349
2693
            raise errors.UnknownSmartMethod(verb)
4350
2694
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4371
2715
        return self._bzrdir._real_bzrdir
4372
2716
 
4373
2717
 
4374
 
error_translators = registry.Registry()
4375
 
no_context_error_translators = registry.Registry()
 
2718
 
 
2719
def _extract_tar(tar, to_dir):
 
2720
    """Extract all the contents of a tarfile object.
 
2721
 
 
2722
    A replacement for extractall, which is not present in python2.4
 
2723
    """
 
2724
    for tarinfo in tar:
 
2725
        tar.extract(tarinfo, to_dir)
4376
2726
 
4377
2727
 
4378
2728
def _translate_error(err, **context):
4392
2742
    def find(name):
4393
2743
        try:
4394
2744
            return context[name]
4395
 
        except KeyError:
4396
 
            mutter('Missing key \'%s\' in context %r', name, context)
 
2745
        except KeyError, key_err:
 
2746
            mutter('Missing key %r in context %r', key_err.args[0], context)
4397
2747
            raise err
4398
 
 
4399
2748
    def get_path():
4400
2749
        """Get the path from the context if present, otherwise use first error
4401
2750
        arg.
4402
2751
        """
4403
2752
        try:
4404
2753
            return context['path']
4405
 
        except KeyError:
 
2754
        except KeyError, key_err:
4406
2755
            try:
4407
 
                return err.error_args[0].decode('utf-8')
4408
 
            except IndexError:
4409
 
                mutter('Missing key \'path\' in context %r', context)
 
2756
                return err.error_args[0]
 
2757
            except IndexError, idx_err:
 
2758
                mutter(
 
2759
                    'Missing key %r in context %r', key_err.args[0], context)
4410
2760
                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')))
 
2761
 
 
2762
    if err.error_verb == 'IncompatibleRepositories':
 
2763
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2764
            err.error_args[1], err.error_args[2])
 
2765
    elif err.error_verb == 'NoSuchRevision':
 
2766
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2767
    elif err.error_verb == 'nosuchrevision':
 
2768
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2769
    elif err.error_tuple == ('nobranch',):
 
2770
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2771
    elif err.error_verb == 'norepository':
 
2772
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2773
    elif err.error_verb == 'LockContention':
 
2774
        raise errors.LockContention('(remote lock)')
 
2775
    elif err.error_verb == 'UnlockableTransport':
 
2776
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2777
    elif err.error_verb == 'LockFailed':
 
2778
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2779
    elif err.error_verb == 'TokenMismatch':
 
2780
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2781
    elif err.error_verb == 'Diverged':
 
2782
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2783
    elif err.error_verb == 'TipChangeRejected':
 
2784
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2785
    elif err.error_verb == 'UnstackableBranchFormat':
 
2786
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2787
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2788
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2789
    elif err.error_verb == 'NotStacked':
 
2790
        raise errors.NotStacked(branch=find('branch'))
 
2791
    elif err.error_verb == 'PermissionDenied':
 
2792
        path = get_path()
 
2793
        if len(err.error_args) >= 2:
 
2794
            extra = err.error_args[1]
 
2795
        else:
 
2796
            extra = None
 
2797
        raise errors.PermissionDenied(path, extra=extra)
 
2798
    elif err.error_verb == 'ReadError':
 
2799
        path = get_path()
 
2800
        raise errors.ReadError(path)
 
2801
    elif err.error_verb == 'NoSuchFile':
 
2802
        path = get_path()
 
2803
        raise errors.NoSuchFile(path)
 
2804
    elif err.error_verb == 'FileExists':
 
2805
        raise errors.FileExists(err.error_args[0])
 
2806
    elif err.error_verb == 'DirectoryNotEmpty':
 
2807
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2808
    elif err.error_verb == 'ShortReadvError':
 
2809
        args = err.error_args
 
2810
        raise errors.ShortReadvError(
 
2811
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2812
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2813
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2814
        val = err.error_args[1]
 
2815
        start = int(err.error_args[2])
 
2816
        end = int(err.error_args[3])
 
2817
        reason = str(err.error_args[4]) # reason must always be a string
 
2818
        if val.startswith('u:'):
 
2819
            val = val[2:].decode('utf-8')
 
2820
        elif val.startswith('s:'):
 
2821
            val = val[2:].decode('base64')
 
2822
        if err.error_verb == 'UnicodeDecodeError':
 
2823
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2824
        elif err.error_verb == 'UnicodeEncodeError':
 
2825
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
2826
    elif err.error_verb == 'ReadOnlyError':
 
2827
        raise errors.TransportNotPossible('readonly transport')
 
2828
    raise errors.UnknownErrorFromSmartServer(err)