/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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,
 
29
    repository,
37
30
    repository as _mod_repository,
 
31
    revision,
38
32
    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 (
 
33
    static_tuple,
 
34
    symbol_versioning,
 
35
)
 
36
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
39
from bzrlib.errors import (
53
40
    NoSuchRevision,
54
41
    SmartProtocolError,
55
42
    )
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
 
43
from bzrlib.lockable_files import LockableFiles
 
44
from bzrlib.smart import client, vfs, repository as smart_repo
 
45
from bzrlib.revision import ensure_null, NULL_REVISION
 
46
from bzrlib.repository import RepositoryWriteLockResult
 
47
from bzrlib.trace import mutter, note, warning
70
48
 
71
49
 
72
50
class _RpcHelper(object):
75
53
    def _call(self, method, *args, **err_context):
76
54
        try:
77
55
            return self._client.call(method, *args)
78
 
        except errors.ErrorFromSmartServer as err:
 
56
        except errors.ErrorFromSmartServer, err:
79
57
            self._translate_error(err, **err_context)
80
58
 
81
59
    def _call_expecting_body(self, method, *args, **err_context):
82
60
        try:
83
61
            return self._client.call_expecting_body(method, *args)
84
 
        except errors.ErrorFromSmartServer as err:
 
62
        except errors.ErrorFromSmartServer, err:
85
63
            self._translate_error(err, **err_context)
86
64
 
87
65
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
88
66
        try:
89
67
            return self._client.call_with_body_bytes(method, args, body_bytes)
90
 
        except errors.ErrorFromSmartServer as err:
 
68
        except errors.ErrorFromSmartServer, err:
91
69
            self._translate_error(err, **err_context)
92
70
 
93
71
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
95
73
        try:
96
74
            return self._client.call_with_body_bytes_expecting_body(
97
75
                method, args, body_bytes)
98
 
        except errors.ErrorFromSmartServer as err:
 
76
        except errors.ErrorFromSmartServer, err:
99
77
            self._translate_error(err, **err_context)
100
78
 
101
79
 
102
80
def response_tuple_to_repo_format(response):
103
81
    """Convert a response tuple describing a repository format to a format."""
104
82
    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')
 
83
    format._rich_root_data = (response[0] == 'yes')
 
84
    format._supports_tree_reference = (response[1] == 'yes')
 
85
    format._supports_external_lookups = (response[2] == 'yes')
108
86
    format._network_name = response[3]
109
87
    return format
110
88
 
111
89
 
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):
 
90
# Note: RemoteBzrDirFormat is in bzrdir.py
 
91
 
 
92
class RemoteBzrDir(BzrDir, _RpcHelper):
419
93
    """Control directory on a remote server, accessed via bzr:// or similar."""
420
94
 
421
95
    def __init__(self, transport, format, _client=None, _force_probe=False):
424
98
        :param _client: Private parameter for testing. Disables probing and the
425
99
            use of a real bzrdir.
426
100
        """
427
 
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
101
        BzrDir.__init__(self, transport, format)
428
102
        # this object holds a delegated bzrdir that uses file-level operations
429
103
        # to talk to the other side
430
104
        self._real_bzrdir = None
460
134
            self._rpc_open(path)
461
135
 
462
136
    def _rpc_open_2_1(self, path):
463
 
        response = self._call(b'BzrDir.open_2.1', path)
464
 
        if response == (b'no',):
 
137
        response = self._call('BzrDir.open_2.1', path)
 
138
        if response == ('no',):
465
139
            raise errors.NotBranchError(path=self.root_transport.base)
466
 
        elif response[0] == b'yes':
467
 
            if response[1] == b'yes':
 
140
        elif response[0] == 'yes':
 
141
            if response[1] == 'yes':
468
142
                self._has_working_tree = True
469
 
            elif response[1] == b'no':
 
143
            elif response[1] == 'no':
470
144
                self._has_working_tree = False
471
145
            else:
472
146
                raise errors.UnexpectedSmartServerResponse(response)
474
148
            raise errors.UnexpectedSmartServerResponse(response)
475
149
 
476
150
    def _rpc_open(self, path):
477
 
        response = self._call(b'BzrDir.open', path)
478
 
        if response not in [(b'yes',), (b'no',)]:
 
151
        response = self._call('BzrDir.open', path)
 
152
        if response not in [('yes',), ('no',)]:
479
153
            raise errors.UnexpectedSmartServerResponse(response)
480
 
        if response == (b'no',):
 
154
        if response == ('no',):
481
155
            raise errors.NotBranchError(path=self.root_transport.base)
482
156
 
483
157
    def _ensure_real(self):
489
163
            if 'hpssvfs' in debug.debug_flags:
490
164
                import traceback
491
165
                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])
 
166
                    ''.join(traceback.format_stack()))
 
167
            self._real_bzrdir = BzrDir.open_from_transport(
 
168
                self.root_transport, _server_formats=False)
495
169
            self._format._network_name = \
496
170
                self._real_bzrdir._format.network_name()
497
171
 
502
176
        # Prevent aliasing problems in the next_open_branch_result cache.
503
177
        # See create_branch for rationale.
504
178
        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
 
179
        return BzrDir.break_lock(self)
548
180
 
549
181
    def _vfs_cloning_metadir(self, require_stacking=False):
550
182
        self._ensure_real()
555
187
        medium = self._client._medium
556
188
        if medium._is_remote_before((1, 13)):
557
189
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
 
        verb = b'BzrDir.cloning_metadir'
 
190
        verb = 'BzrDir.cloning_metadir'
559
191
        if require_stacking:
560
 
            stacking = b'True'
 
192
            stacking = 'True'
561
193
        else:
562
 
            stacking = b'False'
 
194
            stacking = 'False'
563
195
        path = self._path_for_remote_call(self._client)
564
196
        try:
565
197
            response = self._call(verb, path, stacking)
566
198
        except errors.UnknownSmartMethod:
567
199
            medium._remember_remote_is_before((1, 13))
568
200
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
569
 
        except errors.UnknownErrorFromSmartServer as err:
570
 
            if err.error_tuple != (b'BranchReference',):
 
201
        except errors.UnknownErrorFromSmartServer, err:
 
202
            if err.error_tuple != ('BranchReference',):
571
203
                raise
572
204
            # We need to resolve the branch reference to determine the
573
205
            # cloning_metadir.  This causes unnecessary RPCs to open the
574
206
            # referenced branch (and bzrdir, etc) but only when the caller
575
207
            # didn't already resolve the branch reference.
576
208
            referenced_branch = self.open_branch()
577
 
            return referenced_branch.controldir.cloning_metadir()
 
209
            return referenced_branch.bzrdir.cloning_metadir()
578
210
        if len(response) != 3:
579
211
            raise errors.UnexpectedSmartServerResponse(response)
580
212
        control_name, repo_name, branch_info = response
581
213
        if len(branch_info) != 2:
582
214
            raise errors.UnexpectedSmartServerResponse(response)
583
215
        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
 
 
 
216
        format = bzrdir.network_format_registry.get(control_name)
590
217
        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':
 
218
            format.repository_format = repository.network_format_registry.get(
 
219
                repo_name)
 
220
        if branch_ref == 'ref':
598
221
            # XXX: we need possible_transports here to avoid reopening the
599
222
            # connection to the referenced location
600
 
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
 
223
            ref_bzrdir = BzrDir.open(branch_name)
601
224
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
602
225
            format.set_branch_format(branch_format)
603
 
        elif branch_ref == b'branch':
 
226
        elif branch_ref == 'branch':
604
227
            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)
 
228
                format.set_branch_format(
 
229
                    branch.network_format_registry.get(branch_name))
612
230
        else:
613
231
            raise errors.UnexpectedSmartServerResponse(response)
614
232
        return format
624
242
 
625
243
    def destroy_repository(self):
626
244
        """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,))
 
245
        self._ensure_real()
 
246
        self._real_bzrdir.destroy_repository()
637
247
 
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)
 
248
    def create_branch(self, name=None):
644
249
        # as per meta1 formats - just delegate to the format object which may
645
250
        # be parameterised.
646
251
        real_branch = self._format.get_branch_format().initialize(self,
647
 
                                                                  name=name, repository=repository,
648
 
                                                                  append_revisions_only=append_revisions_only)
 
252
            name=name)
649
253
        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)
 
254
            result = RemoteBranch(self, self.find_repository(), real_branch,
 
255
                                  name=name)
655
256
        else:
656
257
            result = real_branch
657
258
        # BzrDir.clone_on_transport() uses the result of create_branch but does
665
266
 
666
267
    def destroy_branch(self, name=None):
667
268
        """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
 
269
        self._ensure_real()
 
270
        self._real_bzrdir.destroy_branch(name=name)
684
271
        self._next_open_branch_result = None
685
 
        if response[0] != b'ok':
686
 
            raise SmartProtocolError(
687
 
                'unexpected response code %s' % (response,))
688
272
 
689
 
    def create_workingtree(self, revision_id=None, from_branch=None,
690
 
                           accelerator_tree=None, hardlink=False):
 
273
    def create_workingtree(self, revision_id=None, from_branch=None):
691
274
        raise errors.NotLocalUrl(self.transport.base)
692
275
 
693
276
    def find_branch_format(self, name=None):
698
281
        b = self.open_branch(name=name)
699
282
        return b._format
700
283
 
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
284
    def get_branch_reference(self, name=None):
748
285
        """See BzrDir.get_branch_reference()."""
749
 
        if name is None:
750
 
            name = self._get_selected_branch()
751
 
        if name != "":
 
286
        if name is not None:
 
287
            # XXX JRV20100304: Support opening colocated branches
752
288
            raise errors.NoColocatedBranchSupport(self)
753
289
        response = self._get_branch_reference()
754
290
        if response[0] == 'ref':
755
 
            return response[1].decode('utf-8')
 
291
            return response[1]
756
292
        else:
757
293
            return None
758
294
 
759
295
    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
296
        path = self._path_for_remote_call(self._client)
767
297
        medium = self._client._medium
768
298
        candidate_calls = [
769
 
            (b'BzrDir.open_branchV3', (2, 1)),
770
 
            (b'BzrDir.open_branchV2', (1, 13)),
771
 
            (b'BzrDir.open_branch', None),
 
299
            ('BzrDir.open_branchV3', (2, 1)),
 
300
            ('BzrDir.open_branchV2', (1, 13)),
 
301
            ('BzrDir.open_branch', None),
772
302
            ]
773
303
        for verb, required_version in candidate_calls:
774
304
            if required_version and medium._is_remote_before(required_version):
781
311
                medium._remember_remote_is_before(required_version)
782
312
            else:
783
313
                break
784
 
        if verb == b'BzrDir.open_branch':
785
 
            if response[0] != b'ok':
 
314
        if verb == 'BzrDir.open_branch':
 
315
            if response[0] != 'ok':
786
316
                raise errors.UnexpectedSmartServerResponse(response)
787
 
            if response[1] != b'':
 
317
            if response[1] != '':
788
318
                return ('ref', response[1])
789
319
            else:
790
 
                return ('branch', b'')
791
 
        if response[0] not in (b'ref', b'branch'):
 
320
                return ('branch', '')
 
321
        if response[0] not in ('ref', 'branch'):
792
322
            raise errors.UnexpectedSmartServerResponse(response)
793
 
        return (response[0].decode('ascii'), response[1])
 
323
        return response
794
324
 
795
325
    def _get_tree_branch(self, name=None):
796
326
        """See BzrDir._get_tree_branch()."""
797
327
        return None, self.open_branch(name=name)
798
328
 
799
 
    def _open_branch(self, name, kind, location_or_format,
800
 
                     ignore_fallbacks=False, possible_transports=None):
801
 
        if kind == 'ref':
 
329
    def open_branch(self, name=None, unsupported=False,
 
330
                    ignore_fallbacks=False):
 
331
        if unsupported:
 
332
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
333
        if self._next_open_branch_result is not None:
 
334
            # See create_branch for details.
 
335
            result = self._next_open_branch_result
 
336
            self._next_open_branch_result = None
 
337
            return result
 
338
        response = self._get_branch_reference()
 
339
        if response[0] == 'ref':
802
340
            # a branch reference, use the existing BranchReference logic.
803
341
            format = BranchReferenceFormat()
804
 
            ref_loc = urlutils.join(self.user_url, location_or_format.decode('utf-8'))
805
342
            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
 
343
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
344
        branch_format_name = response[1]
810
345
        if not branch_format_name:
811
346
            branch_format_name = None
812
347
        format = RemoteBranchFormat(network_name=branch_format_name)
813
348
        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)
 
349
            setup_stacking=not ignore_fallbacks, name=name)
835
350
 
836
351
    def _open_repo_v1(self, path):
837
 
        verb = b'BzrDir.find_repository'
 
352
        verb = 'BzrDir.find_repository'
838
353
        response = self._call(verb, path)
839
 
        if response[0] != b'ok':
 
354
        if response[0] != 'ok':
840
355
            raise errors.UnexpectedSmartServerResponse(response)
841
356
        # servers that only support the v1 method don't support external
842
357
        # references either.
843
358
        self._ensure_real()
844
359
        repo = self._real_bzrdir.open_repository()
845
 
        response = response + (b'no', repo._format.network_name())
 
360
        response = response + ('no', repo._format.network_name())
846
361
        return response, repo
847
362
 
848
363
    def _open_repo_v2(self, path):
849
 
        verb = b'BzrDir.find_repositoryV2'
 
364
        verb = 'BzrDir.find_repositoryV2'
850
365
        response = self._call(verb, path)
851
 
        if response[0] != b'ok':
 
366
        if response[0] != 'ok':
852
367
            raise errors.UnexpectedSmartServerResponse(response)
853
368
        self._ensure_real()
854
369
        repo = self._real_bzrdir.open_repository()
856
371
        return response, repo
857
372
 
858
373
    def _open_repo_v3(self, path):
859
 
        verb = b'BzrDir.find_repositoryV3'
 
374
        verb = 'BzrDir.find_repositoryV3'
860
375
        medium = self._client._medium
861
376
        if medium._is_remote_before((1, 13)):
862
377
            raise errors.UnknownSmartMethod(verb)
865
380
        except errors.UnknownSmartMethod:
866
381
            medium._remember_remote_is_before((1, 13))
867
382
            raise
868
 
        if response[0] != b'ok':
 
383
        if response[0] != 'ok':
869
384
            raise errors.UnexpectedSmartServerResponse(response)
870
385
        return response, None
871
386
 
873
388
        path = self._path_for_remote_call(self._client)
874
389
        response = None
875
390
        for probe in [self._open_repo_v3, self._open_repo_v2,
876
 
                      self._open_repo_v1]:
 
391
            self._open_repo_v1]:
877
392
            try:
878
393
                response, real_repo = probe(path)
879
394
                break
880
395
            except errors.UnknownSmartMethod:
881
396
                pass
882
397
        if response is None:
883
 
            raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
884
 
        if response[0] != b'ok':
 
398
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
 
399
        if response[0] != 'ok':
885
400
            raise errors.UnexpectedSmartServerResponse(response)
886
401
        if len(response) != 6:
887
 
            raise SmartProtocolError(
888
 
                'incorrect response length %s' % (response,))
889
 
        if response[1] == b'':
 
402
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
403
        if response[1] == '':
890
404
            # repo is at this dir.
891
405
            format = response_tuple_to_repo_format(response[2:])
892
406
            # Used to support creating a real format instance when needed.
901
415
 
902
416
    def has_workingtree(self):
903
417
        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')
 
418
            self._ensure_real()
 
419
            self._has_working_tree = self._real_bzrdir.has_workingtree()
915
420
        return self._has_working_tree
916
421
 
917
422
    def open_workingtree(self, recommend_upgrade=True):
922
427
 
923
428
    def _path_for_remote_call(self, client):
924
429
        """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
 
430
        return client.remote_path_from_transport(self.root_transport)
931
431
 
932
432
    def get_branch_transport(self, branch_format, name=None):
933
433
        self._ensure_real()
945
445
        """Upgrading of remote bzrdirs is not supported yet."""
946
446
        return False
947
447
 
948
 
    def needs_format_conversion(self, format):
 
448
    def needs_format_conversion(self, format=None):
949
449
        """Upgrading of remote bzrdirs is not supported yet."""
 
450
        if format is None:
 
451
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
452
                % 'needs_format_conversion(format=None)')
950
453
        return False
951
454
 
 
455
    def clone(self, url, revision_id=None, force_new_repo=False,
 
456
              preserve_stacking=False):
 
457
        self._ensure_real()
 
458
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
459
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
460
 
952
461
    def _get_config(self):
953
462
        return RemoteBzrDirConfig(self)
954
463
 
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):
 
464
 
 
465
class RemoteRepositoryFormat(repository.RepositoryFormat):
994
466
    """Format for repositories accessed over a _SmartClient.
995
467
 
996
468
    Instances of this repository are represented by RemoteRepository
1010
482
        to obtain data like the network name.
1011
483
    """
1012
484
 
1013
 
    _matchingcontroldir = RemoteBzrDirFormat()
1014
 
    supports_full_versioned_files = True
1015
 
    supports_leaving_lock = True
1016
 
    supports_overriding_transport = False
 
485
    _matchingbzrdir = RemoteBzrDirFormat()
1017
486
 
1018
487
    def __init__(self):
1019
 
        _mod_repository.RepositoryFormat.__init__(self)
 
488
        repository.RepositoryFormat.__init__(self)
1020
489
        self._custom_format = None
1021
490
        self._network_name = None
1022
491
        self._creating_bzrdir = None
1023
 
        self._revision_graph_can_have_wrong_parents = None
1024
492
        self._supports_chks = None
1025
493
        self._supports_external_lookups = None
1026
494
        self._supports_tree_reference = None
1027
 
        self._supports_funky_characters = None
1028
 
        self._supports_nesting_repositories = None
1029
495
        self._rich_root_data = None
1030
496
 
1031
497
    def __repr__(self):
1032
498
        return "%s(_network_name=%r)" % (self.__class__.__name__,
1033
 
                                         self._network_name)
 
499
            self._network_name)
1034
500
 
1035
501
    @property
1036
502
    def fast_deltas(self):
1060
526
        return self._supports_external_lookups
1061
527
 
1062
528
    @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
529
    def supports_tree_reference(self):
1080
530
        if self._supports_tree_reference is None:
1081
531
            self._ensure_real()
1083
533
                self._custom_format.supports_tree_reference
1084
534
        return self._supports_tree_reference
1085
535
 
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):
 
536
    def _vfs_initialize(self, a_bzrdir, shared):
1095
537
        """Helper for common code in initialize."""
1096
538
        if self._custom_format:
1097
539
            # Custom format requested
1098
 
            result = self._custom_format.initialize(
1099
 
                a_controldir, shared=shared)
 
540
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
1100
541
        elif self._creating_bzrdir is not None:
1101
542
            # Use the format that the repository we were created to back
1102
543
            # has.
1103
544
            prior_repo = self._creating_bzrdir.open_repository()
1104
545
            prior_repo._ensure_real()
1105
546
            result = prior_repo._real_repository._format.initialize(
1106
 
                a_controldir, shared=shared)
 
547
                a_bzrdir, shared=shared)
1107
548
        else:
1108
549
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
1109
550
            # support remote initialization.
1110
551
            # We delegate to a real object at this point (as RemoteBzrDir
1111
552
            # 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)
 
553
            # recursion if we just called a_bzrdir.create_repository.
 
554
            a_bzrdir._ensure_real()
 
555
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1115
556
        if not isinstance(result, RemoteRepository):
1116
 
            return self.open(a_controldir)
 
557
            return self.open(a_bzrdir)
1117
558
        else:
1118
559
            return result
1119
560
 
1120
 
    def initialize(self, a_controldir, shared=False):
 
561
    def initialize(self, a_bzrdir, shared=False):
1121
562
        # 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
 
563
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
564
            return self._vfs_initialize(a_bzrdir, shared)
 
565
        medium = a_bzrdir._client._medium
1125
566
        if medium._is_remote_before((1, 13)):
1126
 
            return self._vfs_initialize(a_controldir, shared)
 
567
            return self._vfs_initialize(a_bzrdir, shared)
1127
568
        # Creating on a remote bzr dir.
1128
569
        # 1) get the network name to use.
1129
570
        if self._custom_format:
1131
572
        elif self._network_name:
1132
573
            network_name = self._network_name
1133
574
        else:
1134
 
            # Select the current breezy default and ask for that.
1135
 
            reference_bzrdir_format = controldir.format_registry.get(
1136
 
                'default')()
 
575
            # Select the current bzrlib default and ask for that.
 
576
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
1137
577
            reference_format = reference_bzrdir_format.repository_format
1138
578
            network_name = reference_format.network_name()
1139
579
        # 2) try direct creation via RPC
1140
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
1141
 
        verb = b'BzrDir.create_repository'
 
580
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
581
        verb = 'BzrDir.create_repository'
1142
582
        if shared:
1143
 
            shared_str = b'True'
 
583
            shared_str = 'True'
1144
584
        else:
1145
 
            shared_str = b'False'
 
585
            shared_str = 'False'
1146
586
        try:
1147
 
            response = a_controldir._call(verb, path, network_name, shared_str)
 
587
            response = a_bzrdir._call(verb, path, network_name, shared_str)
1148
588
        except errors.UnknownSmartMethod:
1149
589
            # Fallback - use vfs methods
1150
590
            medium._remember_remote_is_before((1, 13))
1151
 
            return self._vfs_initialize(a_controldir, shared)
 
591
            return self._vfs_initialize(a_bzrdir, shared)
1152
592
        else:
1153
593
            # Turn the response into a RemoteRepository object.
1154
594
            format = response_tuple_to_repo_format(response[1:])
1155
595
            # Used to support creating a real format instance when needed.
1156
 
            format._creating_bzrdir = a_controldir
1157
 
            remote_repo = RemoteRepository(a_controldir, format)
 
596
            format._creating_bzrdir = a_bzrdir
 
597
            remote_repo = RemoteRepository(a_bzrdir, format)
1158
598
            format._creating_repo = remote_repo
1159
599
            return remote_repo
1160
600
 
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()
 
601
    def open(self, a_bzrdir):
 
602
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
603
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
604
        return a_bzrdir.open_repository()
1165
605
 
1166
606
    def _ensure_real(self):
1167
607
        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)
 
608
            self._custom_format = repository.network_format_registry.get(
 
609
                self._network_name)
1174
610
 
1175
611
    @property
1176
612
    def _fetch_order(self):
1211
647
        return self._custom_format._serializer
1212
648
 
1213
649
 
1214
 
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1215
 
                       lock._RelockDebugMixin):
 
650
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
 
651
    bzrdir.ControlComponent):
1216
652
    """Repository accessed over rpc.
1217
653
 
1218
654
    For the moment most operations are performed using local transport-backed
1234
670
            self._real_repository = real_repository
1235
671
        else:
1236
672
            self._real_repository = None
1237
 
        self.controldir = remote_bzrdir
 
673
        self.bzrdir = remote_bzrdir
1238
674
        if _client is None:
1239
675
            self._client = remote_bzrdir._client
1240
676
        else:
1242
678
        self._format = format
1243
679
        self._lock_mode = None
1244
680
        self._lock_token = None
1245
 
        self._write_group_tokens = None
1246
681
        self._lock_count = 0
1247
682
        self._leave_lock = False
1248
683
        # Cache of revision parents; misses are cached during read locks, and
1258
693
        self._reconcile_does_inventory_gc = False
1259
694
        self._reconcile_fixes_text_parents = False
1260
695
        self._reconcile_backsup_inventory = False
1261
 
        self.base = self.controldir.transport.base
 
696
        self.base = self.bzrdir.transport.base
1262
697
        # Additional places to query for data.
1263
698
        self._fallback_repositories = []
1264
699
 
1265
700
    @property
1266
701
    def user_transport(self):
1267
 
        return self.controldir.user_transport
 
702
        return self.bzrdir.user_transport
1268
703
 
1269
704
    @property
1270
705
    def control_transport(self):
1271
706
        # XXX: Normally you shouldn't directly get at the remote repository
1272
707
        # transport, but I'm not sure it's worth making this method
1273
708
        # optional -- mbp 2010-04-21
1274
 
        return self.controldir.get_repository_transport(None)
1275
 
 
 
709
        return self.bzrdir.get_repository_transport(None)
 
710
        
1276
711
    def __str__(self):
1277
712
        return "%s(%s)" % (self.__class__.__name__, self.base)
1278
713
 
1288
723
 
1289
724
        :param suppress_errors: see Repository.abort_write_group.
1290
725
        """
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
 
726
        self._ensure_real()
 
727
        return self._real_repository.abort_write_group(
 
728
            suppress_errors=suppress_errors)
1316
729
 
1317
730
    @property
1318
731
    def chk_bytes(self):
1332
745
        for older plugins that don't use e.g. the CommitBuilder
1333
746
        facility.
1334
747
        """
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()
 
748
        self._ensure_real()
 
749
        return self._real_repository.commit_write_group()
1348
750
 
1349
751
    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
 
752
        self._ensure_real()
 
753
        return self._real_repository.resume_write_group(tokens)
1362
754
 
1363
755
    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
 
756
        self._ensure_real()
 
757
        return self._real_repository.suspend_write_group()
1369
758
 
1370
759
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1371
760
        self._ensure_real()
1379
768
 
1380
769
    def get_rev_id_for_revno(self, revno, known_pair):
1381
770
        """See Repository.get_rev_id_for_revno."""
1382
 
        path = self.controldir._path_for_remote_call(self._client)
 
771
        path = self.bzrdir._path_for_remote_call(self._client)
1383
772
        try:
1384
773
            if self._client._medium._is_remote_before((1, 17)):
1385
774
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
1386
775
            response = self._call(
1387
 
                b'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
776
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
1388
777
        except errors.UnknownSmartMethod:
1389
778
            self._client._medium._remember_remote_is_before((1, 17))
1390
779
            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':
 
780
        if response[0] == 'ok':
1406
781
            return True, response[1]
1407
 
        elif response[0] == b'history-incomplete':
 
782
        elif response[0] == 'history-incomplete':
1408
783
            known_pair = response[1:3]
1409
784
            for fallback in self._fallback_repositories:
1410
 
                found, result = fallback.get_rev_id_for_revno(
1411
 
                    revno, known_pair)
 
785
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1412
786
                if found:
1413
787
                    return True, result
1414
788
                else:
1435
809
            if 'hpssvfs' in debug.debug_flags:
1436
810
                import traceback
1437
811
                warning('VFS Repository access triggered\n%s',
1438
 
                        ''.join(traceback.format_stack()))
 
812
                    ''.join(traceback.format_stack()))
1439
813
            self._unstacked_provider.missing_keys.clear()
1440
 
            self.controldir._ensure_real()
 
814
            self.bzrdir._ensure_real()
1441
815
            self._set_real_repository(
1442
 
                self.controldir._real_bzrdir.open_repository())
 
816
                self.bzrdir._real_bzrdir.open_repository())
1443
817
 
1444
818
    def _translate_error(self, err, **context):
1445
 
        self.controldir._translate_error(err, repository=self, **context)
 
819
        self.bzrdir._translate_error(err, repository=self, **context)
1446
820
 
1447
821
    def find_text_key_references(self):
1448
822
        """Find the text key references within the repository.
1449
823
 
 
824
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
825
        revision_ids. Each altered file-ids has the exact revision_ids that
 
826
        altered it listed explicitly.
1450
827
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1451
828
            to whether they were referred to by the inventory of the
1452
829
            revision_id that they contain. The inventory texts from all present
1469
846
    def _get_revision_graph(self, revision_id):
1470
847
        """Private method for using with old (< 1.2) servers to fallback."""
1471
848
        if revision_id is None:
1472
 
            revision_id = b''
1473
 
        elif _mod_revision.is_null(revision_id):
 
849
            revision_id = ''
 
850
        elif revision.is_null(revision_id):
1474
851
            return {}
1475
852
 
1476
 
        path = self.controldir._path_for_remote_call(self._client)
 
853
        path = self.bzrdir._path_for_remote_call(self._client)
1477
854
        response = self._call_expecting_body(
1478
 
            b'Repository.get_revision_graph', path, revision_id)
 
855
            'Repository.get_revision_graph', path, revision_id)
1479
856
        response_tuple, response_handler = response
1480
 
        if response_tuple[0] != b'ok':
 
857
        if response_tuple[0] != 'ok':
1481
858
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1482
859
        coded = response_handler.read_body_bytes()
1483
 
        if coded == b'':
 
860
        if coded == '':
1484
861
            # no revisions in this repository!
1485
862
            return {}
1486
 
        lines = coded.split(b'\n')
 
863
        lines = coded.split('\n')
1487
864
        revision_graph = {}
1488
865
        for line in lines:
1489
866
            d = tuple(line.split())
1499
876
        """Return a source for streaming from this repository."""
1500
877
        return RemoteStreamSource(self, to_format)
1501
878
 
1502
 
    def get_file_graph(self):
1503
 
        with self.lock_read():
1504
 
            return graph.Graph(self.texts)
1505
 
 
 
879
    @needs_read_lock
1506
880
    def has_revision(self, revision_id):
1507
881
        """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,))
 
882
        # Copy of bzrlib.repository.Repository.has_revision
 
883
        return revision_id in self.has_revisions((revision_id,))
1511
884
 
 
885
    @needs_read_lock
1512
886
    def has_revisions(self, revision_ids):
1513
887
        """Probe to find out the presence of multiple revisions.
1514
888
 
1515
889
        :param revision_ids: An iterable of revision_ids.
1516
890
        :return: A set of the revision_ids that were present.
1517
891
        """
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
 
892
        # Copy of bzrlib.repository.Repository.has_revisions
 
893
        parent_map = self.get_parent_map(revision_ids)
 
894
        result = set(parent_map)
 
895
        if _mod_revision.NULL_REVISION in revision_ids:
 
896
            result.add(_mod_revision.NULL_REVISION)
 
897
        return result
1525
898
 
1526
899
    def _has_same_fallbacks(self, other_repo):
1527
900
        """Returns true if the repositories have the same fallbacks."""
1540
913
        # TODO: Move to RepositoryBase and unify with the regular Repository
1541
914
        # one; unfortunately the tests rely on slightly different behaviour at
1542
915
        # present -- mbp 20090710
1543
 
        return (self.__class__ is other.__class__
1544
 
                and self.controldir.transport.base == other.controldir.transport.base)
 
916
        return (self.__class__ is other.__class__ and
 
917
                self.bzrdir.transport.base == other.bzrdir.transport.base)
1545
918
 
1546
919
    def get_graph(self, other_repository=None):
1547
920
        """Return the graph for this repository format"""
1548
921
        parents_provider = self._make_parents_provider(other_repository)
1549
922
        return graph.Graph(parents_provider)
1550
923
 
 
924
    @needs_read_lock
1551
925
    def get_known_graph_ancestry(self, revision_ids):
1552
926
        """Return the known graph for a set of revision ids and their ancestors.
1553
927
        """
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)
 
928
        st = static_tuple.StaticTuple
 
929
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
930
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
931
        return graph.GraphThunkIdsToKeys(known_graph)
1559
932
 
1560
933
    def gather_stats(self, revid=None, committers=None):
1561
934
        """See Repository.gather_stats()."""
1562
 
        path = self.controldir._path_for_remote_call(self._client)
 
935
        path = self.bzrdir._path_for_remote_call(self._client)
1563
936
        # 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''
 
937
        if revid is None or revision.is_null(revid):
 
938
            fmt_revid = ''
1566
939
        else:
1567
940
            fmt_revid = revid
1568
941
        if committers is None or not committers:
1569
 
            fmt_committers = b'no'
 
942
            fmt_committers = 'no'
1570
943
        else:
1571
 
            fmt_committers = b'yes'
 
944
            fmt_committers = 'yes'
1572
945
        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':
 
946
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
947
        if response_tuple[0] != 'ok':
1575
948
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1576
949
 
1577
950
        body = response_handler.read_body_bytes()
1578
951
        result = {}
1579
 
        for line in body.split(b'\n'):
 
952
        for line in body.split('\n'):
1580
953
            if not line:
1581
954
                continue
1582
 
            key, val_text = line.split(b':')
1583
 
            key = key.decode('ascii')
 
955
            key, val_text = line.split(':')
1584
956
            if key in ('revisions', 'size', 'committers'):
1585
957
                result[key] = int(val_text)
1586
958
            elif key in ('firstrev', 'latestrev'):
1587
 
                values = val_text.split(b' ')[1:]
1588
 
                result[key] = (float(values[0]), int(values[1]))
 
959
                values = val_text.split(' ')[1:]
 
960
                result[key] = (float(values[0]), long(values[1]))
1589
961
 
1590
962
        return result
1591
963
 
1597
969
 
1598
970
    def get_physical_lock_status(self):
1599
971
        """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')
 
972
        # should be an API call to the server.
 
973
        self._ensure_real()
 
974
        return self._real_repository.get_physical_lock_status()
1609
975
 
1610
976
    def is_in_write_group(self):
1611
977
        """Return True if there is an open write group.
1612
978
 
1613
979
        write groups are only applicable locally for the smart server..
1614
980
        """
1615
 
        if self._write_group_tokens is not None:
1616
 
            return True
1617
981
        if self._real_repository:
1618
982
            return self._real_repository.is_in_write_group()
1619
983
 
1622
986
 
1623
987
    def is_shared(self):
1624
988
        """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'
 
989
        path = self.bzrdir._path_for_remote_call(self._client)
 
990
        response = self._call('Repository.is_shared', path)
 
991
        if response[0] not in ('yes', 'no'):
 
992
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
993
        return response[0] == 'yes'
1631
994
 
1632
995
    def is_write_locked(self):
1633
996
        return self._lock_mode == 'w'
1640
1003
    def lock_read(self):
1641
1004
        """Lock the repository for read operations.
1642
1005
 
1643
 
        :return: A breezy.lock.LogicalLockResult.
 
1006
        :return: A bzrlib.lock.LogicalLockResult.
1644
1007
        """
1645
1008
        # wrong eventually - want a local lock cache context
1646
1009
        if not self._lock_mode:
1657
1020
        return lock.LogicalLockResult(self.unlock)
1658
1021
 
1659
1022
    def _remote_lock_write(self, token):
1660
 
        path = self.controldir._path_for_remote_call(self._client)
 
1023
        path = self.bzrdir._path_for_remote_call(self._client)
1661
1024
        if token is None:
1662
 
            token = b''
 
1025
            token = ''
1663
1026
        err_context = {'token': token}
1664
 
        response = self._call(b'Repository.lock_write', path, token,
 
1027
        response = self._call('Repository.lock_write', path, token,
1665
1028
                              **err_context)
1666
 
        if response[0] == b'ok':
 
1029
        if response[0] == 'ok':
1667
1030
            ok, token = response
1668
1031
            return token
1669
1032
        else:
1741
1104
        # 3) new servers, RemoteRepository.ensure_real is triggered before
1742
1105
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1743
1106
        # and need to populate it.
1744
 
        if (self._fallback_repositories
1745
 
            and len(self._real_repository._fallback_repositories)
1746
 
                != len(self._fallback_repositories)):
 
1107
        if (self._fallback_repositories and
 
1108
            len(self._real_repository._fallback_repositories) !=
 
1109
            len(self._fallback_repositories)):
1747
1110
            if len(self._real_repository._fallback_repositories):
1748
1111
                raise AssertionError(
1749
1112
                    "cannot cleanly remove existing _fallback_repositories")
1755
1118
            self._real_repository.lock_write(self._lock_token)
1756
1119
        elif self._lock_mode == 'r':
1757
1120
            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
1121
 
1763
1122
    def start_write_group(self):
1764
1123
        """Start a write group on the decorated repository.
1768
1127
        for older plugins that don't use e.g. the CommitBuilder
1769
1128
        facility.
1770
1129
        """
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]]
 
1130
        self._ensure_real()
 
1131
        return self._real_repository.start_write_group()
1789
1132
 
1790
1133
    def _unlock(self, token):
1791
 
        path = self.controldir._path_for_remote_call(self._client)
 
1134
        path = self.bzrdir._path_for_remote_call(self._client)
1792
1135
        if not token:
1793
1136
            # with no token the remote repository is not persistently locked.
1794
1137
            return
1795
1138
        err_context = {'token': token}
1796
 
        response = self._call(b'Repository.unlock', path, token,
 
1139
        response = self._call('Repository.unlock', path, token,
1797
1140
                              **err_context)
1798
 
        if response == (b'ok',):
 
1141
        if response == ('ok',):
1799
1142
            return
1800
1143
        else:
1801
1144
            raise errors.UnexpectedSmartServerResponse(response)
1818
1161
            # This is just to let the _real_repository stay up to date.
1819
1162
            if self._real_repository is not None:
1820
1163
                self._real_repository.unlock()
1821
 
            elif self._write_group_tokens is not None:
1822
 
                self.abort_write_group()
1823
1164
        finally:
1824
1165
            # The rpc-level lock should be released even if there was a
1825
1166
            # problem releasing the vfs-based lock.
1837
1178
 
1838
1179
    def break_lock(self):
1839
1180
        # 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)
 
1181
        self._ensure_real()
 
1182
        return self._real_repository.break_lock()
1848
1183
 
1849
1184
    def _get_tarball(self, compression):
1850
1185
        """Return a TemporaryFile containing a repository tarball.
1852
1187
        Returns None if the server does not support sending tarballs.
1853
1188
        """
1854
1189
        import tempfile
1855
 
        path = self.controldir._path_for_remote_call(self._client)
 
1190
        path = self.bzrdir._path_for_remote_call(self._client)
1856
1191
        try:
1857
1192
            response, protocol = self._call_expecting_body(
1858
 
                b'Repository.tarball', path, compression.encode('ascii'))
 
1193
                'Repository.tarball', path, compression)
1859
1194
        except errors.UnknownSmartMethod:
1860
1195
            protocol.cancel_read_body()
1861
1196
            return None
1862
 
        if response[0] == b'ok':
 
1197
        if response[0] == 'ok':
1863
1198
            # Extract the tarball and return it
1864
1199
            t = tempfile.NamedTemporaryFile()
1865
1200
            # TODO: rpc layer should read directly into it...
1869
1204
        raise errors.UnexpectedSmartServerResponse(response)
1870
1205
 
1871
1206
    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()
 
1207
        # TODO: Option to control what format is created?
 
1208
        self._ensure_real()
 
1209
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
1210
                                                             shared=False)
 
1211
        dest_repo.fetch(self, revision_id=revision_id)
1893
1212
        return dest_repo
1894
1213
 
1895
 
    # These methods are just thin shims to the VFS object for now.
 
1214
    ### These methods are just thin shims to the VFS object for now.
1896
1215
 
1897
1216
    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]
 
1217
        self._ensure_real()
 
1218
        return self._real_repository.revision_tree(revision_id)
1905
1219
 
1906
1220
    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]
 
1221
        self._ensure_real()
 
1222
        return self._real_repository.get_serializer_format()
1917
1223
 
1918
1224
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1919
1225
                           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
 
1226
                           revision_id=None):
 
1227
        # FIXME: It ought to be possible to call this without immediately
 
1228
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
1229
        self._ensure_real()
 
1230
        real_repo = self._real_repository
 
1231
        builder = real_repo.get_commit_builder(branch, parents,
 
1232
                config, timestamp=timestamp, timezone=timezone,
 
1233
                committer=committer, revprops=revprops, revision_id=revision_id)
 
1234
        return builder
1944
1235
 
1945
1236
    def add_fallback_repository(self, repository):
1946
1237
        """Add a repository to use for looking up data not held locally.
1953
1244
        # We need to accumulate additional repositories here, to pass them in
1954
1245
        # on various RPC's.
1955
1246
        #
1956
 
        # Make the check before we lock: this raises an exception.
1957
 
        self._check_fallback_repository(repository)
1958
1247
        if self.is_locked():
1959
1248
            # We will call fallback.unlock() when we transition to the unlocked
1960
1249
            # state, so always add a lock here. If a caller passes us a locked
1961
1250
            # repository, they are responsible for unlocking it later.
1962
1251
            repository.lock_read()
 
1252
        self._check_fallback_repository(repository)
1963
1253
        self._fallback_repositories.append(repository)
1964
1254
        # If self._real_repository was parameterised already (e.g. because a
1965
1255
        # _real_branch had its get_stacked_on_url method called), then the
1966
1256
        # repository to be added may already be in the _real_repositories list.
1967
1257
        if self._real_repository is not None:
1968
1258
            fallback_locations = [repo.user_url for repo in
1969
 
                                  self._real_repository._fallback_repositories]
 
1259
                self._real_repository._fallback_repositories]
1970
1260
            if repository.user_url not in fallback_locations:
1971
1261
                self._real_repository.add_fallback_repository(repository)
1972
1262
 
1985
1275
        return self._real_repository.add_inventory(revid, inv, parents)
1986
1276
 
1987
1277
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1988
 
                               parents, basis_inv=None, propagate_caches=False):
 
1278
            parents, basis_inv=None, propagate_caches=False):
1989
1279
        self._ensure_real()
1990
1280
        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
 
 
 
1281
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1282
            propagate_caches=propagate_caches)
 
1283
 
 
1284
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
1285
        self._ensure_real()
 
1286
        return self._real_repository.add_revision(
 
1287
            rev_id, rev, inv=inv, config=config)
 
1288
 
 
1289
    @needs_read_lock
2020
1290
    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
1291
        self._ensure_real()
2069
 
        return self._real_repository._iter_inventories(revision_ids, ordering)
 
1292
        return self._real_repository.get_inventory(revision_id)
2070
1293
 
2071
1294
    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
 
 
 
1295
        self._ensure_real()
 
1296
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1297
 
 
1298
    @needs_read_lock
2147
1299
    def get_revision(self, revision_id):
2148
 
        with self.lock_read():
2149
 
            return self.get_revisions([revision_id])[0]
 
1300
        self._ensure_real()
 
1301
        return self._real_repository.get_revision(revision_id)
2150
1302
 
2151
1303
    def get_transaction(self):
2152
1304
        self._ensure_real()
2153
1305
        return self._real_repository.get_transaction()
2154
1306
 
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
 
1307
    @needs_read_lock
 
1308
    def clone(self, a_bzrdir, revision_id=None):
 
1309
        self._ensure_real()
 
1310
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2161
1311
 
2162
1312
    def make_working_trees(self):
2163
1313
        """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'
 
1314
        self._ensure_real()
 
1315
        return self._real_repository.make_working_trees()
2174
1316
 
2175
1317
    def refresh_data(self):
2176
1318
        """Re-read any data needed to synchronise with disk.
2178
1320
        This method is intended to be called after another repository instance
2179
1321
        (such as one used by a smart server) has inserted data into the
2180
1322
        repository. On all repositories this will work outside of write groups.
2181
 
        Some repository formats (pack and newer for breezy native formats)
 
1323
        Some repository formats (pack and newer for bzrlib native formats)
2182
1324
        support refresh_data inside write groups. If called inside a write
2183
1325
        group on a repository that does not support refreshing in a write group
2184
1326
        IsInWriteGroupError will be raised.
2185
1327
        """
2186
1328
        if self._real_repository is not None:
2187
1329
            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
1330
 
2192
1331
    def revision_ids_to_search_result(self, result_set):
2193
1332
        """Convert a set of revision ids to a graph SearchResult."""
2194
1333
        result_parents = set()
2195
 
        for parents in self.get_graph().get_parent_map(result_set).values():
 
1334
        for parents in self.get_graph().get_parent_map(
 
1335
            result_set).itervalues():
2196
1336
            result_parents.update(parents)
2197
1337
        included_keys = result_set.intersection(result_parents)
2198
1338
        start_keys = result_set.difference(included_keys)
2199
1339
        exclude_keys = result_parents.difference(result_set)
2200
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
2201
 
                                        len(result_set), result_set)
 
1340
        result = graph.SearchResult(start_keys, exclude_keys,
 
1341
            len(result_set), result_set)
2202
1342
        return result
2203
1343
 
2204
 
    def search_missing_revision_ids(self, other,
2205
 
                                    find_ghosts=True, revision_ids=None, if_present_ids=None,
2206
 
                                    limit=None):
 
1344
    @needs_read_lock
 
1345
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
2207
1346
        """Return the revision ids that other has that this does not.
2208
1347
 
2209
1348
        These are returned in topological order.
2210
1349
 
2211
1350
        revision_id: only return revision ids included by revision_id.
2212
1351
        """
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)
 
1352
        return repository.InterRepository.get(
 
1353
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
2218
1354
 
2219
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
2220
 
              fetch_spec=None, lossy=False):
 
1355
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1356
            fetch_spec=None):
2221
1357
        # No base implementation to use as RemoteRepository is not a subclass
2222
1358
        # of Repository; so this is a copy of Repository.fetch().
2223
1359
        if fetch_spec is not None and revision_id is not None:
2227
1363
            raise errors.InternalBzrError(
2228
1364
                "May not fetch while in a write group.")
2229
1365
        # 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)):
 
1366
        if (self.has_same_location(source)
 
1367
            and fetch_spec is None
 
1368
            and self._has_same_fallbacks(source)):
2233
1369
            # check that last_revision is in 'from' and then return a
2234
1370
            # no-operation.
2235
 
            if (revision_id is not None
2236
 
                    and not _mod_revision.is_null(revision_id)):
 
1371
            if (revision_id is not None and
 
1372
                not revision.is_null(revision_id)):
2237
1373
                self.get_revision(revision_id)
2238
 
            return _mod_repository.FetchResult(0)
 
1374
            return 0, []
2239
1375
        # if there is no specific appropriate InterRepository, this will get
2240
1376
        # the InterRepository base class, which raises an
2241
1377
        # 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)
 
1378
        inter = repository.InterRepository.get(source, self)
 
1379
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1380
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2250
1381
 
2251
1382
    def create_bundle(self, target, base, fileobj, format=None):
2252
1383
        self._ensure_real()
2253
1384
        self._real_repository.create_bundle(target, base, fileobj, format)
2254
1385
 
 
1386
    @needs_read_lock
 
1387
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1388
        self._ensure_real()
 
1389
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
1390
 
2255
1391
    def fileids_altered_by_revision_ids(self, revision_ids):
2256
1392
        self._ensure_real()
2257
1393
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2261
1397
        return self._real_repository._get_versioned_file_checker(
2262
1398
            revisions, revision_versions_cache)
2263
1399
 
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
1400
    def iter_files_bytes(self, desired_files):
2316
1401
        """See Repository.iter_file_bytes.
2317
1402
        """
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)
 
1403
        self._ensure_real()
 
1404
        return self._real_repository.iter_files_bytes(desired_files)
2347
1405
 
2348
1406
    def get_parent_map(self, revision_ids):
2349
 
        """See breezy.Graph.get_parent_map()."""
 
1407
        """See bzrlib.Graph.get_parent_map()."""
2350
1408
        return self._make_parents_provider().get_parent_map(revision_ids)
2351
1409
 
2352
1410
    def _get_parent_map_rpc(self, keys):
2371
1429
            # There is one other "bug" which is that ghosts in
2372
1430
            # get_revision_graph() are not returned at all. But we won't worry
2373
1431
            # about that for now.
2374
 
            for node_id, parent_ids in rg.items():
 
1432
            for node_id, parent_ids in rg.iteritems():
2375
1433
                if parent_ids == ():
2376
1434
                    rg[node_id] = (NULL_REVISION,)
2377
1435
            rg[NULL_REVISION] = ()
2382
1440
            raise ValueError('get_parent_map(None) is not valid')
2383
1441
        if NULL_REVISION in keys:
2384
1442
            keys.discard(NULL_REVISION)
2385
 
            found_parents = {NULL_REVISION: ()}
 
1443
            found_parents = {NULL_REVISION:()}
2386
1444
            if not keys:
2387
1445
                return found_parents
2388
1446
        else:
2408
1466
        if parents_map is None:
2409
1467
            # Repository is not locked, so there's no cache.
2410
1468
            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)
 
1469
        # start_set is all the keys in the cache
 
1470
        start_set = set(parents_map)
 
1471
        # result set is all the references to keys in the cache
 
1472
        result_parents = set()
 
1473
        for parents in parents_map.itervalues():
 
1474
            result_parents.update(parents)
 
1475
        stop_keys = result_parents.difference(start_set)
 
1476
        # We don't need to send ghosts back to the server as a position to
 
1477
        # stop either.
 
1478
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1479
        key_count = len(parents_map)
 
1480
        if (NULL_REVISION in result_parents
 
1481
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1482
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1483
            # in our cache of "missing" keys we need to increment our key count
 
1484
            # by 1, because the reconsitituted SearchResult on the server will
 
1485
            # still consider NULL_REVISION to be an included key.
 
1486
            key_count += 1
 
1487
        included_keys = start_set.intersection(result_parents)
 
1488
        start_set.difference_update(included_keys)
2420
1489
        recipe = ('manual', start_set, stop_keys, key_count)
2421
1490
        body = self._serialise_search_recipe(recipe)
2422
 
        path = self.controldir._path_for_remote_call(self._client)
 
1491
        path = self.bzrdir._path_for_remote_call(self._client)
2423
1492
        for key in keys:
2424
 
            if not isinstance(key, bytes):
 
1493
            if type(key) is not str:
2425
1494
                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)
 
1495
                    "key %r not a plain string" % (key,))
 
1496
        verb = 'Repository.get_parent_map'
 
1497
        args = (path, 'include-missing:') + tuple(keys)
2429
1498
        try:
2430
1499
            response = self._call_with_body_bytes_expecting_body(
2431
1500
                verb, args, body)
2444
1513
            # Recurse just once and we should use the fallback code.
2445
1514
            return self._get_parent_map_rpc(keys)
2446
1515
        response_tuple, response_handler = response
2447
 
        if response_tuple[0] not in [b'ok']:
 
1516
        if response_tuple[0] not in ['ok']:
2448
1517
            response_handler.cancel_read_body()
2449
1518
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2450
 
        if response_tuple[0] == b'ok':
 
1519
        if response_tuple[0] == 'ok':
2451
1520
            coded = bz2.decompress(response_handler.read_body_bytes())
2452
 
            if coded == b'':
 
1521
            if coded == '':
2453
1522
                # no revisions found
2454
1523
                return {}
2455
 
            lines = coded.split(b'\n')
 
1524
            lines = coded.split('\n')
2456
1525
            revision_graph = {}
2457
1526
            for line in lines:
2458
1527
                d = tuple(line.split())
2460
1529
                    revision_graph[d[0]] = d[1:]
2461
1530
                else:
2462
1531
                    # No parents:
2463
 
                    if d[0].startswith(b'missing:'):
 
1532
                    if d[0].startswith('missing:'):
2464
1533
                        revid = d[0][8:]
2465
1534
                        self._unstacked_provider.note_missing_key(revid)
2466
1535
                    else:
2469
1538
                        revision_graph[d[0]] = (NULL_REVISION,)
2470
1539
            return revision_graph
2471
1540
 
 
1541
    @needs_read_lock
2472
1542
    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()
 
1543
        self._ensure_real()
 
1544
        return self._real_repository.get_signature_text(revision_id)
2492
1545
 
 
1546
    @needs_read_lock
2493
1547
    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)
 
1548
        self._ensure_real()
 
1549
        return self._real_repository._get_inventory_xml(revision_id)
2499
1550
 
2500
1551
    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
 
1552
        self._ensure_real()
 
1553
        return self._real_repository.reconcile(other=other, thorough=thorough)
2528
1554
 
2529
1555
    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
 
 
 
1556
        self._ensure_real()
 
1557
        return self._real_repository.all_revision_ids()
 
1558
 
 
1559
    @needs_read_lock
 
1560
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1561
        self._ensure_real()
 
1562
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1563
            specific_fileids=specific_fileids)
 
1564
 
 
1565
    @needs_read_lock
 
1566
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1567
        self._ensure_real()
 
1568
        return self._real_repository.get_revision_delta(revision_id,
 
1569
            specific_fileids=specific_fileids)
 
1570
 
 
1571
    @needs_read_lock
2565
1572
    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)
 
1573
        self._ensure_real()
 
1574
        return self._real_repository.revision_trees(revision_ids)
2570
1575
 
 
1576
    @needs_read_lock
2571
1577
    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)
 
1578
        self._ensure_real()
 
1579
        return self._real_repository.get_revision_reconcile(revision_id)
2575
1580
 
 
1581
    @needs_read_lock
2576
1582
    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)
 
1583
        self._ensure_real()
 
1584
        return self._real_repository.check(revision_ids=revision_ids,
 
1585
            callback_refs=callback_refs, check_repo=check_repo)
2581
1586
 
2582
1587
    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)
 
1588
        self._ensure_real()
 
1589
        return self._real_repository.copy_content_into(
 
1590
            destination, revision_id=revision_id)
2590
1591
 
2591
1592
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2592
1593
        # get a tarball of the remote repository, and copy from that into the
2593
1594
        # destination
 
1595
        from bzrlib import osutils
2594
1596
        import tarfile
2595
1597
        # TODO: Maybe a progress bar while streaming the tarball?
2596
 
        note(gettext("Copying repository content as tarball..."))
 
1598
        note("Copying repository content as tarball...")
2597
1599
        tar_file = self._get_tarball('bz2')
2598
1600
        if tar_file is None:
2599
1601
            return None
2600
1602
        destination = to_bzrdir.create_repository()
2601
1603
        try:
2602
1604
            tar = tarfile.open('repository', fileobj=tar_file,
2603
 
                               mode='r|bz2')
 
1605
                mode='r|bz2')
2604
1606
            tmpdir = osutils.mkdtemp()
2605
1607
            try:
2606
 
                tar.extractall(tmpdir)
2607
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
1608
                _extract_tar(tar, tmpdir)
 
1609
                tmp_bzrdir = BzrDir.open(tmpdir)
2608
1610
                tmp_repo = tmp_bzrdir.open_repository()
2609
1611
                tmp_repo.copy_content_into(destination, revision_id)
2610
1612
            finally:
2625
1627
        self._ensure_real()
2626
1628
        return self._real_repository.inventories
2627
1629
 
 
1630
    @needs_write_lock
2628
1631
    def pack(self, hint=None, clean_obsolete_packs=False):
2629
1632
        """Compress the data within the repository.
 
1633
 
 
1634
        This is not currently implemented within the smart server.
2630
1635
        """
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)
 
1636
        self._ensure_real()
 
1637
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
2648
1638
 
2649
1639
    @property
2650
1640
    def revisions(self):
2651
1641
        """Decorate the real repository for now.
2652
1642
 
 
1643
        In the short term this should become a real object to intercept graph
 
1644
        lookups.
 
1645
 
2653
1646
        In the long term a full blown network facility is needed.
2654
1647
        """
2655
1648
        self._ensure_real()
2657
1650
 
2658
1651
    def set_make_working_trees(self, new_value):
2659
1652
        if new_value:
2660
 
            new_value_str = b"True"
 
1653
            new_value_str = "True"
2661
1654
        else:
2662
 
            new_value_str = b"False"
2663
 
        path = self.controldir._path_for_remote_call(self._client)
 
1655
            new_value_str = "False"
 
1656
        path = self.bzrdir._path_for_remote_call(self._client)
2664
1657
        try:
2665
1658
            response = self._call(
2666
 
                b'Repository.set_make_working_trees', path, new_value_str)
 
1659
                'Repository.set_make_working_trees', path, new_value_str)
2667
1660
        except errors.UnknownSmartMethod:
2668
1661
            self._ensure_real()
2669
1662
            self._real_repository.set_make_working_trees(new_value)
2670
1663
        else:
2671
 
            if response[0] != b'ok':
 
1664
            if response[0] != 'ok':
2672
1665
                raise errors.UnexpectedSmartServerResponse(response)
2673
1666
 
2674
1667
    @property
2681
1674
        self._ensure_real()
2682
1675
        return self._real_repository.signatures
2683
1676
 
 
1677
    @needs_write_lock
2684
1678
    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)
 
1679
        self._ensure_real()
 
1680
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2690
1681
 
2691
1682
    @property
2692
1683
    def texts(self):
2698
1689
        self._ensure_real()
2699
1690
        return self._real_repository.texts
2700
1691
 
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
 
1692
    @needs_read_lock
 
1693
    def get_revisions(self, revision_ids):
 
1694
        self._ensure_real()
 
1695
        return self._real_repository.get_revisions(revision_ids)
2751
1696
 
2752
1697
    def supports_rich_root(self):
2753
1698
        return self._format.rich_root_data
2754
1699
 
 
1700
    def iter_reverse_revision_history(self, revision_id):
 
1701
        self._ensure_real()
 
1702
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
1703
 
2755
1704
    @property
2756
1705
    def _serializer(self):
2757
1706
        return self._format._serializer
2758
1707
 
2759
1708
    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)
 
1709
        self._ensure_real()
 
1710
        return self._real_repository.store_revision_signature(
 
1711
            gpg_strategy, plaintext, revision_id)
2763
1712
 
2764
1713
    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:]]
 
1714
        self._ensure_real()
 
1715
        return self._real_repository.add_signature_text(revision_id, signature)
2784
1716
 
2785
1717
    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)
 
1718
        self._ensure_real()
 
1719
        return self._real_repository.has_signature_for_revision_id(revision_id)
2817
1720
 
2818
1721
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2819
1722
        self._ensure_real()
2820
1723
        return self._real_repository.item_keys_introduced_by(revision_ids,
2821
 
                                                             _files_pb=_files_pb)
 
1724
            _files_pb=_files_pb)
 
1725
 
 
1726
    def revision_graph_can_have_wrong_parents(self):
 
1727
        # The answer depends on the remote repo format.
 
1728
        self._ensure_real()
 
1729
        return self._real_repository.revision_graph_can_have_wrong_parents()
2822
1730
 
2823
1731
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2824
1732
        self._ensure_real()
2833
1741
        providers = [self._unstacked_provider]
2834
1742
        if other is not None:
2835
1743
            providers.insert(0, other)
2836
 
        return graph.StackedParentsProvider(_LazyListJoin(
2837
 
            providers, self._fallback_repositories))
 
1744
        providers.extend(r._make_parents_provider() for r in
 
1745
                         self._fallback_repositories)
 
1746
        return graph.StackedParentsProvider(providers)
2838
1747
 
2839
1748
    def _serialise_search_recipe(self, recipe):
2840
1749
        """Serialise a graph search recipe.
2842
1751
        :param recipe: A search recipe (start, stop, count).
2843
1752
        :return: Serialised bytes.
2844
1753
        """
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))
 
1754
        start_keys = ' '.join(recipe[1])
 
1755
        stop_keys = ' '.join(recipe[2])
 
1756
        count = str(recipe[3])
 
1757
        return '\n'.join((start_keys, stop_keys, count))
2849
1758
 
2850
1759
    def _serialise_search_result(self, search_result):
2851
 
        parts = search_result.get_network_struct()
2852
 
        return b'\n'.join(parts)
 
1760
        if isinstance(search_result, graph.PendingAncestryResult):
 
1761
            parts = ['ancestry-of']
 
1762
            parts.extend(search_result.heads)
 
1763
        else:
 
1764
            recipe = search_result.get_recipe()
 
1765
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1766
        return '\n'.join(parts)
2853
1767
 
2854
1768
    def autopack(self):
2855
 
        path = self.controldir._path_for_remote_call(self._client)
 
1769
        path = self.bzrdir._path_for_remote_call(self._client)
2856
1770
        try:
2857
 
            response = self._call(b'PackRepository.autopack', path)
 
1771
            response = self._call('PackRepository.autopack', path)
2858
1772
        except errors.UnknownSmartMethod:
2859
1773
            self._ensure_real()
2860
1774
            self._real_repository._pack_collection.autopack()
2861
1775
            return
2862
1776
        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):
 
1777
        if response[0] != 'ok':
 
1778
            raise errors.UnexpectedSmartServerResponse(response)
 
1779
 
 
1780
 
 
1781
class RemoteStreamSink(repository.StreamSink):
2905
1782
 
2906
1783
    def _insert_real(self, stream, src_format, resume_tokens):
2907
1784
        self.target_repo._ensure_real()
2911
1788
            self.target_repo.autopack()
2912
1789
        return result
2913
1790
 
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
1791
    def insert_stream(self, stream, src_format, resume_tokens):
2926
1792
        target = self.target_repo
2927
1793
        target._unstacked_provider.missing_keys.clear()
2928
 
        candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
 
1794
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2929
1795
        if target._lock_token:
2930
 
            candidate_calls.append(
2931
 
                (b'Repository.insert_stream_locked', (1, 14)))
2932
 
            lock_args = (target._lock_token or b'',)
 
1796
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1797
            lock_args = (target._lock_token or '',)
2933
1798
        else:
2934
 
            candidate_calls.append((b'Repository.insert_stream', (1, 13)))
 
1799
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
2935
1800
            lock_args = ()
2936
1801
        client = target._client
2937
1802
        medium = client._medium
2938
 
        path = target.controldir._path_for_remote_call(client)
 
1803
        path = target.bzrdir._path_for_remote_call(client)
2939
1804
        # Probe for the verb to use with an empty stream before sending the
2940
1805
        # real stream to it.  We do this both to avoid the risk of sending a
2941
1806
        # large request that is then rejected, and because we don't want to
2952
1817
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2953
1818
            try:
2954
1819
                response = client.call_with_body_stream(
2955
 
                    (verb, path, b'') + lock_args, byte_stream)
 
1820
                    (verb, path, '') + lock_args, byte_stream)
2956
1821
            except errors.UnknownSmartMethod:
2957
1822
                medium._remember_remote_is_before(required_version)
2958
1823
            else:
2971
1836
            stream = self._stop_stream_if_inventory_delta(stream)
2972
1837
        byte_stream = smart_repo._stream_to_byte_stream(
2973
1838
            stream, src_format)
2974
 
        resume_tokens = b' '.join([token.encode('utf-8')
2975
 
                                   for token in resume_tokens])
 
1839
        resume_tokens = ' '.join(resume_tokens)
2976
1840
        response = client.call_with_body_stream(
2977
1841
            (verb, path, resume_tokens) + lock_args, byte_stream)
2978
 
        if response[0][0] not in (b'ok', b'missing-basis'):
 
1842
        if response[0][0] not in ('ok', 'missing-basis'):
2979
1843
            raise errors.UnexpectedSmartServerResponse(response)
2980
1844
        if self._last_substream is not None:
2981
1845
            # The stream included an inventory-delta record, but the remote
2983
1847
            # rest of the stream via VFS.
2984
1848
            self.target_repo.refresh_data()
2985
1849
            return self._resume_stream_with_vfs(response, src_format)
2986
 
        if response[0][0] == b'missing-basis':
 
1850
        if response[0][0] == 'missing-basis':
2987
1851
            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)
 
1852
            resume_tokens = tokens
 
1853
            return resume_tokens, set(missing_keys)
2990
1854
        else:
2991
1855
            self.target_repo.refresh_data()
2992
1856
            return [], set()
2995
1859
        """Resume sending a stream via VFS, first resending the record and
2996
1860
        substream that couldn't be sent via an insert_stream verb.
2997
1861
        """
2998
 
        if response[0][0] == b'missing-basis':
 
1862
        if response[0][0] == 'missing-basis':
2999
1863
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3000
 
            tokens = [token.decode('utf-8') for token in tokens]
3001
1864
            # Ignore missing_keys, we haven't finished inserting yet
3002
1865
        else:
3003
1866
            tokens = []
3004
 
 
3005
1867
        def resume_substream():
3006
1868
            # Yield the substream that was interrupted.
3007
1869
            for record in self._last_substream:
3008
1870
                yield record
3009
1871
            self._last_substream = None
3010
 
 
3011
1872
        def resume_stream():
3012
1873
            # Finish sending the interrupted substream
3013
1874
            yield ('inventory-deltas', resume_substream())
3024
1885
        self._last_substream and self._last_stream so that the stream can be
3025
1886
        resumed by _resume_stream_with_vfs.
3026
1887
        """
3027
 
 
 
1888
                    
3028
1889
        stream_iter = iter(stream)
3029
1890
        for substream_kind, substream in stream_iter:
3030
1891
            if substream_kind == 'inventory-deltas':
3033
1894
                return
3034
1895
            else:
3035
1896
                yield substream_kind, substream
3036
 
 
3037
 
 
3038
 
class RemoteStreamSource(vf_repository.StreamSource):
 
1897
            
 
1898
 
 
1899
class RemoteStreamSource(repository.StreamSource):
3039
1900
    """Stream data from a remote server."""
3040
1901
 
3041
1902
    def get_stream(self, search):
3042
 
        if (self.from_repository._fallback_repositories
3043
 
                and self.to_format._fetch_order == 'topological'):
 
1903
        if (self.from_repository._fallback_repositories and
 
1904
            self.to_format._fetch_order == 'topological'):
3044
1905
            return self._real_stream(self.from_repository, search)
3045
1906
        sources = []
3046
1907
        seen = set()
3054
1915
            sources.append(repo)
3055
1916
        return self.missing_parents_chain(search, sources)
3056
1917
 
3057
 
    def _get_real_stream_for_missing_keys(self, missing_keys):
 
1918
    def get_stream_for_missing_keys(self, missing_keys):
3058
1919
        self.from_repository._ensure_real()
3059
1920
        real_repo = self.from_repository._real_repository
3060
1921
        real_source = real_repo._get_source(self.to_format)
3061
1922
        return real_source.get_stream_for_missing_keys(missing_keys)
3062
1923
 
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
1924
    def _real_stream(self, repo, search):
3091
1925
        """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
 
1926
        
 
1927
        This never called RemoteStreamSource.get_stream, and is a heler
 
1928
        for RemoteStreamSource._get_stream to allow getting a stream 
3095
1929
        reliably whether fallback back because of old servers or trying
3096
1930
        to stream from a non-RemoteRepository (which the stacked support
3097
1931
        code will do).
3122
1956
            return self._real_stream(repo, search)
3123
1957
        client = repo._client
3124
1958
        medium = client._medium
3125
 
        path = repo.controldir._path_for_remote_call(client)
 
1959
        path = repo.bzrdir._path_for_remote_call(client)
3126
1960
        search_bytes = repo._serialise_search_result(search)
3127
1961
        args = (path, self.to_format.network_name())
3128
1962
        candidate_verbs = [
3129
 
            (b'Repository.get_stream_1.19', (1, 19)),
3130
 
            (b'Repository.get_stream', (1, 13))]
3131
 
 
 
1963
            ('Repository.get_stream_1.19', (1, 19)),
 
1964
            ('Repository.get_stream', (1, 13))]
3132
1965
        found_verb = False
3133
1966
        for verb, version in candidate_verbs:
3134
1967
            if medium._is_remote_before(version):
3138
1971
                    verb, args, search_bytes)
3139
1972
            except errors.UnknownSmartMethod:
3140
1973
                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
1974
            else:
3153
1975
                response_tuple, response_handler = response
3154
1976
                found_verb = True
3155
1977
                break
3156
1978
        if not found_verb:
3157
1979
            return self._real_stream(repo, search)
3158
 
        if response_tuple[0] != b'ok':
 
1980
        if response_tuple[0] != 'ok':
3159
1981
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3160
1982
        byte_stream = response_handler.read_streamed_body()
3161
1983
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3162
 
                                                               self._record_counter)
 
1984
            self._record_counter)
3163
1985
        if src_format.network_name() != repo._format.network_name():
3164
1986
            raise AssertionError(
3165
1987
                "Mismatched RemoteRepository and stream src %r, %r" % (
3166
 
                    src_format.network_name(), repo._format.network_name()))
 
1988
                src_format.network_name(), repo._format.network_name()))
3167
1989
        return stream
3168
1990
 
3169
1991
    def missing_parents_chain(self, search, sources):
3209
2031
    """
3210
2032
 
3211
2033
    def __init__(self, bzrdir, _client):
3212
 
        self.controldir = bzrdir
 
2034
        self.bzrdir = bzrdir
3213
2035
        self._client = _client
3214
2036
        self._need_find_modes = True
3215
2037
        LockableFiles.__init__(
3226
2048
 
3227
2049
    def __init__(self, network_name=None):
3228
2050
        super(RemoteBranchFormat, self).__init__()
3229
 
        self._matchingcontroldir = RemoteBzrDirFormat()
3230
 
        self._matchingcontroldir.set_branch_format(self)
 
2051
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2052
        self._matchingbzrdir.set_branch_format(self)
3231
2053
        self._custom_format = None
3232
2054
        self._network_name = network_name
3233
2055
 
3234
2056
    def __eq__(self, other):
3235
 
        return (isinstance(other, RemoteBranchFormat)
3236
 
                and self.__dict__ == other.__dict__)
 
2057
        return (isinstance(other, RemoteBranchFormat) and
 
2058
            self.__dict__ == other.__dict__)
3237
2059
 
3238
2060
    def _ensure_real(self):
3239
2061
        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)
 
2062
            self._custom_format = branch.network_format_registry.get(
 
2063
                self._network_name)
3246
2064
 
3247
2065
    def get_format_description(self):
3248
2066
        self._ensure_real()
3251
2069
    def network_name(self):
3252
2070
        return self._network_name
3253
2071
 
3254
 
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
3255
 
        return a_controldir.open_branch(name=name,
3256
 
                                        ignore_fallbacks=ignore_fallbacks)
 
2072
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2073
        return a_bzrdir.open_branch(name=name, 
 
2074
            ignore_fallbacks=ignore_fallbacks)
3257
2075
 
3258
 
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3259
 
                        repository=None):
 
2076
    def _vfs_initialize(self, a_bzrdir, name):
3260
2077
        # Initialisation when using a local bzrdir object, or a non-vfs init
3261
2078
        # method is not available on the server.
3262
2079
        # self._custom_format is always set - the start of initialize ensures
3263
2080
        # 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)
 
2081
        if isinstance(a_bzrdir, RemoteBzrDir):
 
2082
            a_bzrdir._ensure_real()
 
2083
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2084
                name)
3269
2085
        else:
3270
2086
            # 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,
 
2087
            result = self._custom_format.initialize(a_bzrdir, name)
 
2088
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
2089
            not isinstance(result, RemoteBranch)):
 
2090
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3277
2091
                                  name=name)
3278
2092
        return result
3279
2093
 
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()
 
2094
    def initialize(self, a_bzrdir, name=None):
3284
2095
        # 1) get the network name to use.
3285
2096
        if self._custom_format:
3286
2097
            network_name = self._custom_format.network_name()
3287
2098
        else:
3288
 
            # Select the current breezy default and ask for that.
3289
 
            reference_bzrdir_format = controldir.format_registry.get(
3290
 
                'default')()
 
2099
            # Select the current bzrlib default and ask for that.
 
2100
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
3291
2101
            reference_format = reference_bzrdir_format.get_branch_format()
3292
2102
            self._custom_format = reference_format
3293
2103
            network_name = reference_format.network_name()
3294
2104
        # 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
 
2105
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
2106
            return self._vfs_initialize(a_bzrdir, name=name)
 
2107
        medium = a_bzrdir._client._medium
3300
2108
        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)
 
2109
            return self._vfs_initialize(a_bzrdir, name=name)
3304
2110
        # Creating on a remote bzr dir.
3305
2111
        # 2) try direct creation via RPC
3306
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
3307
 
        if name != "":
 
2112
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2113
        if name is not None:
3308
2114
            # XXX JRV20100304: Support creating colocated branches
3309
2115
            raise errors.NoColocatedBranchSupport(self)
3310
 
        verb = b'BzrDir.create_branch'
 
2116
        verb = 'BzrDir.create_branch'
3311
2117
        try:
3312
 
            response = a_controldir._call(verb, path, network_name)
 
2118
            response = a_bzrdir._call(verb, path, network_name)
3313
2119
        except errors.UnknownSmartMethod:
3314
2120
            # Fallback - use vfs methods
3315
2121
            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':
 
2122
            return self._vfs_initialize(a_bzrdir, name=name)
 
2123
        if response[0] != 'ok':
3320
2124
            raise errors.UnexpectedSmartServerResponse(response)
3321
2125
        # Turn the response into a RemoteRepository object.
3322
2126
        format = RemoteBranchFormat(network_name=response[1])
3323
2127
        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
 
2128
        if response[2] == '':
 
2129
            repo_bzrdir = a_bzrdir
3335
2130
        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)
 
2131
            repo_bzrdir = RemoteBzrDir(
 
2132
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2133
                a_bzrdir._client)
 
2134
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2135
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2136
            format=format, setup_stacking=False, name=name)
3348
2137
        # XXX: We know this is a new branch, so it must have revno 0, revid
3349
2138
        # NULL_REVISION. Creating the branch locked would make this be unable
3350
2139
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3369
2158
        self._ensure_real()
3370
2159
        return self._custom_format.supports_set_append_revisions_only()
3371
2160
 
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
2161
 
3442
2162
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3443
2163
    """Branch stored on a server accessed by HPSS RPC.
3446
2166
    """
3447
2167
 
3448
2168
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3449
 
                 _client=None, format=None, setup_stacking=True, name=None,
3450
 
                 possible_transports=None):
 
2169
        _client=None, format=None, setup_stacking=True, name=None):
3451
2170
        """Create a RemoteBranch instance.
3452
2171
 
3453
2172
        :param real_branch: An optional local implementation of the branch
3464
2183
        # We intentionally don't call the parent class's __init__, because it
3465
2184
        # will try to assign to self.tags, which is a property in this subclass.
3466
2185
        # And the parent's __init__ doesn't do much anyway.
3467
 
        self.controldir = remote_bzrdir
3468
 
        self.name = name
 
2186
        self.bzrdir = remote_bzrdir
3469
2187
        if _client is not None:
3470
2188
            self._client = _client
3471
2189
        else:
3483
2201
            self._real_branch.repository = self.repository
3484
2202
        else:
3485
2203
            self._real_branch = None
3486
 
        # Fill out expected attributes of branch for breezy API users.
 
2204
        # Fill out expected attributes of branch for bzrlib API users.
3487
2205
        self._clear_cached_state()
3488
2206
        # TODO: deprecate self.base in favor of user_url
3489
 
        self.base = self.controldir.user_url
 
2207
        self.base = self.bzrdir.user_url
3490
2208
        self._name = name
3491
2209
        self._control_files = None
3492
2210
        self._lock_mode = None
3494
2212
        self._repo_lock_token = None
3495
2213
        self._lock_count = 0
3496
2214
        self._leave_lock = False
3497
 
        self.conf_store = None
3498
2215
        # Setup a format: note that we cannot call _ensure_real until all the
3499
2216
        # attributes above are set: This code cannot be moved higher up in this
3500
2217
        # function.
3520
2237
            hook(self)
3521
2238
        self._is_stacked = False
3522
2239
        if setup_stacking:
3523
 
            self._setup_stacking(possible_transports)
 
2240
            self._setup_stacking()
3524
2241
 
3525
 
    def _setup_stacking(self, possible_transports):
 
2242
    def _setup_stacking(self):
3526
2243
        # configure stacking into the remote repository, by reading it from
3527
2244
        # the vfs branch.
3528
2245
        try:
3529
2246
            fallback_url = self.get_stacked_on_url()
3530
 
        except (errors.NotStacked, branch.UnstackableBranchFormat,
3531
 
                errors.UnstackableRepositoryFormat) as e:
 
2247
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2248
            errors.UnstackableRepositoryFormat), e:
3532
2249
            return
3533
2250
        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)
 
2251
        self._activate_fallback_location(fallback_url)
3541
2252
 
3542
2253
    def _get_config(self):
3543
2254
        return RemoteBranchConfig(self)
3544
2255
 
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
2256
    def _get_real_transport(self):
3559
2257
        # if we try vfs access, return the real branch's vfs transport
3560
2258
        self._ensure_real()
3575
2273
        if self._real_branch is None:
3576
2274
            if not vfs.vfs_enabled():
3577
2275
                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(
 
2276
                    'to use vfs implementation')
 
2277
            self.bzrdir._ensure_real()
 
2278
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3581
2279
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3582
 
            # The remote branch and the real branch shares the same store. If
3583
 
            # we don't, there will always be cases where one of the stores
3584
 
            # doesn't see an update made on the other.
3585
 
            self._real_branch.conf_store = self.conf_store
3586
2280
            if self.repository._real_repository is None:
3587
2281
                # Give the remote repository the matching real repo.
3588
2282
                real_repo = self._real_branch.repository
3603
2297
 
3604
2298
    def _clear_cached_state(self):
3605
2299
        super(RemoteBranch, self)._clear_cached_state()
3606
 
        self._tags_bytes = None
3607
2300
        if self._real_branch is not None:
3608
2301
            self._real_branch._clear_cached_state()
3609
2302
 
3625
2318
        # because it triggers an _ensure_real that we otherwise might not need.
3626
2319
        if self._control_files is None:
3627
2320
            self._control_files = RemoteBranchLockableFiles(
3628
 
                self.controldir, self._client)
 
2321
                self.bzrdir, self._client)
3629
2322
        return self._control_files
3630
2323
 
 
2324
    def _get_checkout_format(self):
 
2325
        self._ensure_real()
 
2326
        return self._real_branch._get_checkout_format()
 
2327
 
3631
2328
    def get_physical_lock_status(self):
3632
2329
        """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')
 
2330
        # should be an API call to the server, as branches must be lockable.
 
2331
        self._ensure_real()
 
2332
        return self._real_branch.get_physical_lock_status()
3642
2333
 
3643
2334
    def get_stacked_on_url(self):
3644
2335
        """Get the URL this branch is stacked against.
3652
2343
        try:
3653
2344
            # there may not be a repository yet, so we can't use
3654
2345
            # 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:
 
2346
            response = self._client.call('Branch.get_stacked_on_url',
 
2347
                self._remote_path())
 
2348
        except errors.ErrorFromSmartServer, err:
3658
2349
            # there may not be a repository yet, so we can't call through
3659
2350
            # its _translate_error
3660
2351
            _translate_error(err, branch=self)
3661
 
        except errors.UnknownSmartMethod as err:
 
2352
        except errors.UnknownSmartMethod, err:
3662
2353
            self._ensure_real()
3663
2354
            return self._real_branch.get_stacked_on_url()
3664
 
        if response[0] != b'ok':
 
2355
        if response[0] != 'ok':
3665
2356
            raise errors.UnexpectedSmartServerResponse(response)
3666
 
        return response[1].decode('utf-8')
 
2357
        return response[1]
3667
2358
 
3668
2359
    def set_stacked_on_url(self, url):
3669
2360
        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
2361
        if not url:
3678
2362
            self._is_stacked = False
3679
2363
        else:
3680
2364
            self._is_stacked = True
3681
 
 
 
2365
        
3682
2366
    def _vfs_get_tags_bytes(self):
3683
2367
        self._ensure_real()
3684
2368
        return self._real_branch._get_tags_bytes()
3685
2369
 
3686
2370
    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
2371
        medium = self._client._medium
3694
2372
        if medium._is_remote_before((1, 13)):
3695
2373
            return self._vfs_get_tags_bytes()
3696
2374
        try:
3697
 
            response = self._call(
3698
 
                b'Branch.get_tags_bytes', self._remote_path())
 
2375
            response = self._call('Branch.get_tags_bytes', self._remote_path())
3699
2376
        except errors.UnknownSmartMethod:
3700
2377
            medium._remember_remote_is_before((1, 13))
3701
2378
            return self._vfs_get_tags_bytes()
3706
2383
        return self._real_branch._set_tags_bytes(bytes)
3707
2384
 
3708
2385
    def _set_tags_bytes(self, bytes):
3709
 
        if self.is_locked():
3710
 
            self._tags_bytes = bytes
3711
2386
        medium = self._client._medium
3712
2387
        if medium._is_remote_before((1, 18)):
3713
2388
            self._vfs_set_tags_bytes(bytes)
3716
2391
            args = (
3717
2392
                self._remote_path(), self._lock_token, self._repo_lock_token)
3718
2393
            response = self._call_with_body_bytes(
3719
 
                b'Branch.set_tags_bytes', args, bytes)
 
2394
                'Branch.set_tags_bytes', args, bytes)
3720
2395
        except errors.UnknownSmartMethod:
3721
2396
            medium._remember_remote_is_before((1, 18))
3722
2397
            self._vfs_set_tags_bytes(bytes)
3724
2399
    def lock_read(self):
3725
2400
        """Lock the branch for read operations.
3726
2401
 
3727
 
        :return: A breezy.lock.LogicalLockResult.
 
2402
        :return: A bzrlib.lock.LogicalLockResult.
3728
2403
        """
3729
2404
        self.repository.lock_read()
3730
2405
        if not self._lock_mode:
3739
2414
 
3740
2415
    def _remote_lock_write(self, token):
3741
2416
        if token is None:
3742
 
            branch_token = repo_token = b''
 
2417
            branch_token = repo_token = ''
3743
2418
        else:
3744
2419
            branch_token = token
3745
2420
            repo_token = self.repository.lock_write().repository_token
3747
2422
        err_context = {'token': token}
3748
2423
        try:
3749
2424
            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:
 
2425
                'Branch.lock_write', self._remote_path(), branch_token,
 
2426
                repo_token or '', **err_context)
 
2427
        except errors.LockContention, e:
3753
2428
            # The LockContention from the server doesn't have any
3754
2429
            # information about the lock_url. We re-raise LockContention
3755
2430
            # with valid lock_url.
3756
2431
            raise errors.LockContention('(remote lock)',
3757
 
                                        self.repository.base.split('.bzr/')[0])
3758
 
        if response[0] != b'ok':
 
2432
                self.repository.base.split('.bzr/')[0])
 
2433
        if response[0] != 'ok':
3759
2434
            raise errors.UnexpectedSmartServerResponse(response)
3760
2435
        ok, branch_token, repo_token = response
3761
2436
        return branch_token, repo_token
3767
2442
            remote_tokens = self._remote_lock_write(token)
3768
2443
            self._lock_token, self._repo_lock_token = remote_tokens
3769
2444
            if not self._lock_token:
3770
 
                raise SmartProtocolError(
3771
 
                    'Remote server did not return a token!')
 
2445
                raise SmartProtocolError('Remote server did not return a token!')
3772
2446
            # Tell the self.repository object that it is locked.
3773
2447
            self.repository.lock_write(
3774
2448
                self._repo_lock_token, _skip_rpc=True)
3798
2472
    def _unlock(self, branch_token, repo_token):
3799
2473
        err_context = {'token': str((branch_token, repo_token))}
3800
2474
        response = self._call(
3801
 
            b'Branch.unlock', self._remote_path(), branch_token,
3802
 
            repo_token or b'', **err_context)
3803
 
        if response == (b'ok',):
 
2475
            'Branch.unlock', self._remote_path(), branch_token,
 
2476
            repo_token or '', **err_context)
 
2477
        if response == ('ok',):
3804
2478
            return
3805
2479
        raise errors.UnexpectedSmartServerResponse(response)
3806
2480
 
3809
2483
        try:
3810
2484
            self._lock_count -= 1
3811
2485
            if not self._lock_count:
3812
 
                if self.conf_store is not None:
3813
 
                    self.conf_store.save_changes()
3814
2486
                self._clear_cached_state()
3815
2487
                mode = self._lock_mode
3816
2488
                self._lock_mode = None
3817
2489
                if self._real_branch is not None:
3818
 
                    if (not self._leave_lock and mode == 'w'
3819
 
                            and self._repo_lock_token):
 
2490
                    if (not self._leave_lock and mode == 'w' and
 
2491
                        self._repo_lock_token):
3820
2492
                        # If this RemoteBranch will remove the physical lock
3821
2493
                        # for the repository, make sure the _real_branch
3822
2494
                        # doesn't do it first.  (Because the _real_branch's
3839
2511
            self.repository.unlock()
3840
2512
 
3841
2513
    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)
 
2514
        self._ensure_real()
 
2515
        return self._real_branch.break_lock()
3850
2516
 
3851
2517
    def leave_lock_in_place(self):
3852
2518
        if not self._lock_token:
3858
2524
            raise NotImplementedError(self.dont_leave_lock_in_place)
3859
2525
        self._leave_lock = False
3860
2526
 
 
2527
    @needs_read_lock
3861
2528
    def get_rev_id(self, revno, history=None):
3862
2529
        if revno == 0:
3863
2530
            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)
 
2531
        last_revision_info = self.last_revision_info()
 
2532
        ok, result = self.repository.get_rev_id_for_revno(
 
2533
            revno, last_revision_info)
 
2534
        if ok:
 
2535
            return result
 
2536
        missing_parent = result[1]
 
2537
        # Either the revision named by the server is missing, or its parent
 
2538
        # is.  Call get_parent_map to determine which, so that we report a
 
2539
        # useful error.
 
2540
        parent_map = self.repository.get_parent_map([missing_parent])
 
2541
        if missing_parent in parent_map:
 
2542
            missing_parent = parent_map[missing_parent]
 
2543
        raise errors.RevisionNotPresent(missing_parent, self.repository)
3881
2544
 
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,))
 
2545
    def _last_revision_info(self):
 
2546
        response = self._call('Branch.last_revision_info', self._remote_path())
 
2547
        if response[0] != 'ok':
 
2548
            raise SmartProtocolError('unexpected response code %s' % (response,))
3888
2549
        revno = int(response[1])
3889
2550
        last_revision = response[2]
3890
2551
        return (revno, last_revision)
3895
2556
            self._ensure_real()
3896
2557
            return self._real_branch._gen_revision_history()
3897
2558
        response_tuple, response_handler = self._call_expecting_body(
3898
 
            b'Branch.revision_history', self._remote_path())
3899
 
        if response_tuple[0] != b'ok':
 
2559
            'Branch.revision_history', self._remote_path())
 
2560
        if response_tuple[0] != 'ok':
3900
2561
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3901
 
        result = response_handler.read_body_bytes().split(b'\x00')
 
2562
        result = response_handler.read_body_bytes().split('\x00')
3902
2563
        if result == ['']:
3903
2564
            return []
3904
2565
        return result
3905
2566
 
3906
2567
    def _remote_path(self):
3907
 
        return self.controldir._path_for_remote_call(self._client)
 
2568
        return self.bzrdir._path_for_remote_call(self._client)
3908
2569
 
3909
2570
    def _set_last_revision_descendant(self, revision_id, other_branch,
3910
 
                                      allow_diverged=False, allow_overwrite_descendant=False):
 
2571
            allow_diverged=False, allow_overwrite_descendant=False):
3911
2572
        # This performs additional work to meet the hook contract; while its
3912
2573
        # undesirable, we have to synthesise the revno to call the hook, and
3913
2574
        # not calling the hook is worse as it means changes can't be prevented.
3918
2579
        history = self._lefthand_history(revision_id)
3919
2580
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3920
2581
        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)
 
2582
        response = self._call('Branch.set_last_revision_ex',
 
2583
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2584
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
2585
            **err_context)
3926
2586
        self._clear_cached_state()
3927
 
        if len(response) != 3 and response[0] != b'ok':
 
2587
        if len(response) != 3 and response[0] != 'ok':
3928
2588
            raise errors.UnexpectedSmartServerResponse(response)
3929
2589
        new_revno, new_revision_id = response[1:]
3930
2590
        self._last_revision_info_cache = new_revno, new_revision_id
3944
2604
        history = self._lefthand_history(revision_id)
3945
2605
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3946
2606
        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',):
 
2607
        response = self._call('Branch.set_last_revision',
 
2608
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2609
            revision_id)
 
2610
        if response != ('ok',):
3951
2611
            raise errors.UnexpectedSmartServerResponse(response)
3952
2612
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3953
2613
 
 
2614
    @needs_write_lock
 
2615
    def set_revision_history(self, rev_history):
 
2616
        # Send just the tip revision of the history; the server will generate
 
2617
        # the full history from that.  If the revision doesn't exist in this
 
2618
        # branch, NoSuchRevision will be raised.
 
2619
        if rev_history == []:
 
2620
            rev_id = 'null:'
 
2621
        else:
 
2622
            rev_id = rev_history[-1]
 
2623
        self._set_last_revision(rev_id)
 
2624
        for hook in branch.Branch.hooks['set_rh']:
 
2625
            hook(self, rev_history)
 
2626
        self._cache_revision_history(rev_history)
 
2627
 
3954
2628
    def _get_parent_location(self):
3955
2629
        medium = self._client._medium
3956
2630
        if medium._is_remote_before((1, 13)):
3957
2631
            return self._vfs_get_parent_location()
3958
2632
        try:
3959
 
            response = self._call(b'Branch.get_parent', self._remote_path())
 
2633
            response = self._call('Branch.get_parent', self._remote_path())
3960
2634
        except errors.UnknownSmartMethod:
3961
2635
            medium._remember_remote_is_before((1, 13))
3962
2636
            return self._vfs_get_parent_location()
3963
2637
        if len(response) != 1:
3964
2638
            raise errors.UnexpectedSmartServerResponse(response)
3965
2639
        parent_location = response[0]
3966
 
        if parent_location == b'':
 
2640
        if parent_location == '':
3967
2641
            return None
3968
 
        return parent_location.decode('utf-8')
 
2642
        return parent_location
3969
2643
 
3970
2644
    def _vfs_get_parent_location(self):
3971
2645
        self._ensure_real()
3976
2650
        if medium._is_remote_before((1, 15)):
3977
2651
            return self._vfs_set_parent_location(url)
3978
2652
        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)
 
2653
            call_url = url or ''
 
2654
            if type(call_url) is not str:
 
2655
                raise AssertionError('url must be a str or None (%s)' % url)
 
2656
            response = self._call('Branch.set_parent_location',
 
2657
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2658
                call_url)
3985
2659
        except errors.UnknownSmartMethod:
3986
2660
            medium._remember_remote_is_before((1, 15))
3987
2661
            return self._vfs_set_parent_location(url)
3992
2666
        self._ensure_real()
3993
2667
        return self._real_branch._set_parent_location(url)
3994
2668
 
 
2669
    @needs_write_lock
3995
2670
    def pull(self, source, overwrite=False, stop_revision=None,
3996
2671
             **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
 
2672
        self._clear_cached_state_of_remote_branch_only()
 
2673
        self._ensure_real()
 
2674
        return self._real_branch.pull(
 
2675
            source, overwrite=overwrite, stop_revision=stop_revision,
 
2676
            _override_hook_target=self, **kwargs)
 
2677
 
 
2678
    @needs_read_lock
 
2679
    def push(self, target, overwrite=False, stop_revision=None):
 
2680
        self._ensure_real()
 
2681
        return self._real_branch.push(
 
2682
            target, overwrite=overwrite, stop_revision=stop_revision,
 
2683
            _override_hook_source_branch=self)
4013
2684
 
4014
2685
    def is_locked(self):
4015
2686
        return self._lock_count >= 1
4016
2687
 
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
 
 
 
2688
    @needs_read_lock
4042
2689
    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
 
 
 
2690
        self._ensure_real()
 
2691
        return self._real_branch.revision_id_to_revno(revision_id)
 
2692
 
 
2693
    @needs_write_lock
4061
2694
    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)
 
2695
        # XXX: These should be returned by the set_last_revision_info verb
 
2696
        old_revno, old_revid = self.last_revision_info()
 
2697
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2698
        revision_id = ensure_null(revision_id)
 
2699
        try:
 
2700
            response = self._call('Branch.set_last_revision_info',
 
2701
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2702
                str(revno), revision_id)
 
2703
        except errors.UnknownSmartMethod:
 
2704
            self._ensure_real()
 
2705
            self._clear_cached_state_of_remote_branch_only()
 
2706
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2707
            self._last_revision_info_cache = revno, revision_id
 
2708
            return
 
2709
        if response == ('ok',):
 
2710
            self._clear_cached_state()
 
2711
            self._last_revision_info_cache = revno, revision_id
 
2712
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2713
            # Update the _real_branch's cache too.
 
2714
            if self._real_branch is not None:
 
2715
                cache = self._last_revision_info_cache
 
2716
                self._real_branch._last_revision_info_cache = cache
 
2717
        else:
 
2718
            raise errors.UnexpectedSmartServerResponse(response)
4089
2719
 
 
2720
    @needs_write_lock
4090
2721
    def generate_revision_history(self, revision_id, last_rev=None,
4091
2722
                                  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)
 
2723
        medium = self._client._medium
 
2724
        if not medium._is_remote_before((1, 6)):
 
2725
            # Use a smart method for 1.6 and above servers
 
2726
            try:
 
2727
                self._set_last_revision_descendant(revision_id, other_branch,
 
2728
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2729
                return
 
2730
            except errors.UnknownSmartMethod:
 
2731
                medium._remember_remote_is_before((1, 6))
 
2732
        self._clear_cached_state_of_remote_branch_only()
 
2733
        self.set_revision_history(self._lefthand_history(revision_id,
 
2734
            last_rev=last_rev,other_branch=other_branch))
4116
2735
 
4117
2736
    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)
 
2737
        self._ensure_real()
 
2738
        return self._real_branch.set_push_location(location)
4208
2739
 
4209
2740
 
4210
2741
class RemoteConfig(object):
4212
2743
 
4213
2744
    It is a low-level object that considers config data to be name/value pairs
4214
2745
    that may be associated with a section. Assigning meaning to the these
4215
 
    values is done at higher levels like breezy.config.TreeConfig.
 
2746
    values is done at higher levels like bzrlib.config.TreeConfig.
4216
2747
    """
4217
2748
 
4218
2749
    def get_option(self, name, section=None, default=None):
4225
2756
        """
4226
2757
        try:
4227
2758
            configobj = self._get_configobj()
4228
 
            section_obj = None
4229
2759
            if section is None:
4230
2760
                section_obj = configobj
4231
2761
            else:
4232
2762
                try:
4233
2763
                    section_obj = configobj[section]
4234
2764
                except KeyError:
4235
 
                    pass
4236
 
            if section_obj is None:
4237
 
                value = default
4238
 
            else:
4239
 
                value = section_obj.get(name, default)
 
2765
                    return default
 
2766
            return section_obj.get(name, default)
4240
2767
        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
 
2768
            return self._vfs_get_option(name, section, default)
4245
2769
 
4246
2770
    def _response_to_configobj(self, response):
4247
 
        if len(response[0]) and response[0][0] != b'ok':
 
2771
        if len(response[0]) and response[0][0] != 'ok':
4248
2772
            raise errors.UnexpectedSmartServerResponse(response)
4249
2773
        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
 
2774
        return config.ConfigObj(lines, encoding='utf-8')
4254
2775
 
4255
2776
 
4256
2777
class RemoteBranchConfig(RemoteConfig):
4262
2783
    def _get_configobj(self):
4263
2784
        path = self._branch._remote_path()
4264
2785
        response = self._branch._client.call_expecting_body(
4265
 
            b'Branch.get_config_file', path)
 
2786
            'Branch.get_config_file', path)
4266
2787
        return self._response_to_configobj(response)
4267
2788
 
4268
2789
    def set_option(self, value, name, section=None):
4283
2804
            return self._set_config_option(value, name, section)
4284
2805
 
4285
2806
    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
2807
        try:
4293
2808
            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'))
 
2809
            response = self._branch._client.call('Branch.set_config_option',
 
2810
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2811
                value.encode('utf8'), name, section or '')
4298
2812
        except errors.UnknownSmartMethod:
4299
2813
            medium = self._branch._client._medium
4300
2814
            medium._remember_remote_is_before((1, 14))
4305
2819
    def _serialize_option_dict(self, option_dict):
4306
2820
        utf8_dict = {}
4307
2821
        for key, value in option_dict.items():
4308
 
            if isinstance(key, str):
 
2822
            if isinstance(key, unicode):
4309
2823
                key = key.encode('utf8')
4310
 
            if isinstance(value, str):
 
2824
            if isinstance(value, unicode):
4311
2825
                value = value.encode('utf8')
4312
2826
            utf8_dict[key] = value
4313
2827
        return bencode.bencode(utf8_dict)
4317
2831
            path = self._branch._remote_path()
4318
2832
            serialised_dict = self._serialize_option_dict(value)
4319
2833
            response = self._branch._client.call(
4320
 
                b'Branch.set_config_option_dict',
 
2834
                'Branch.set_config_option_dict',
4321
2835
                path, self._branch._lock_token, self._branch._repo_lock_token,
4322
 
                serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
 
2836
                serialised_dict, name, section or '')
4323
2837
        except errors.UnknownSmartMethod:
4324
2838
            medium = self._branch._client._medium
4325
2839
            medium._remember_remote_is_before((2, 2))
4344
2858
 
4345
2859
    def _get_configobj(self):
4346
2860
        medium = self._bzrdir._client._medium
4347
 
        verb = b'BzrDir.get_config_file'
 
2861
        verb = 'BzrDir.get_config_file'
4348
2862
        if medium._is_remote_before((1, 15)):
4349
2863
            raise errors.UnknownSmartMethod(verb)
4350
2864
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4371
2885
        return self._bzrdir._real_bzrdir
4372
2886
 
4373
2887
 
4374
 
error_translators = registry.Registry()
4375
 
no_context_error_translators = registry.Registry()
 
2888
 
 
2889
def _extract_tar(tar, to_dir):
 
2890
    """Extract all the contents of a tarfile object.
 
2891
 
 
2892
    A replacement for extractall, which is not present in python2.4
 
2893
    """
 
2894
    for tarinfo in tar:
 
2895
        tar.extract(tarinfo, to_dir)
4376
2896
 
4377
2897
 
4378
2898
def _translate_error(err, **context):
4392
2912
    def find(name):
4393
2913
        try:
4394
2914
            return context[name]
4395
 
        except KeyError:
4396
 
            mutter('Missing key \'%s\' in context %r', name, context)
 
2915
        except KeyError, key_err:
 
2916
            mutter('Missing key %r in context %r', key_err.args[0], context)
4397
2917
            raise err
4398
 
 
4399
2918
    def get_path():
4400
2919
        """Get the path from the context if present, otherwise use first error
4401
2920
        arg.
4402
2921
        """
4403
2922
        try:
4404
2923
            return context['path']
4405
 
        except KeyError:
 
2924
        except KeyError, key_err:
4406
2925
            try:
4407
 
                return err.error_args[0].decode('utf-8')
4408
 
            except IndexError:
4409
 
                mutter('Missing key \'path\' in context %r', context)
 
2926
                return err.error_args[0]
 
2927
            except IndexError, idx_err:
 
2928
                mutter(
 
2929
                    'Missing key %r in context %r', key_err.args[0], context)
4410
2930
                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')))
 
2931
 
 
2932
    if err.error_verb == 'IncompatibleRepositories':
 
2933
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2934
            err.error_args[1], err.error_args[2])
 
2935
    elif err.error_verb == 'NoSuchRevision':
 
2936
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2937
    elif err.error_verb == 'nosuchrevision':
 
2938
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2939
    elif err.error_verb == 'nobranch':
 
2940
        if len(err.error_args) >= 1:
 
2941
            extra = err.error_args[0]
 
2942
        else:
 
2943
            extra = None
 
2944
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2945
            detail=extra)
 
2946
    elif err.error_verb == 'norepository':
 
2947
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2948
    elif err.error_verb == 'LockContention':
 
2949
        raise errors.LockContention('(remote lock)')
 
2950
    elif err.error_verb == 'UnlockableTransport':
 
2951
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2952
    elif err.error_verb == 'LockFailed':
 
2953
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2954
    elif err.error_verb == 'TokenMismatch':
 
2955
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2956
    elif err.error_verb == 'Diverged':
 
2957
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2958
    elif err.error_verb == 'TipChangeRejected':
 
2959
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2960
    elif err.error_verb == 'UnstackableBranchFormat':
 
2961
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2962
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2963
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2964
    elif err.error_verb == 'NotStacked':
 
2965
        raise errors.NotStacked(branch=find('branch'))
 
2966
    elif err.error_verb == 'PermissionDenied':
 
2967
        path = get_path()
 
2968
        if len(err.error_args) >= 2:
 
2969
            extra = err.error_args[1]
 
2970
        else:
 
2971
            extra = None
 
2972
        raise errors.PermissionDenied(path, extra=extra)
 
2973
    elif err.error_verb == 'ReadError':
 
2974
        path = get_path()
 
2975
        raise errors.ReadError(path)
 
2976
    elif err.error_verb == 'NoSuchFile':
 
2977
        path = get_path()
 
2978
        raise errors.NoSuchFile(path)
 
2979
    elif err.error_verb == 'FileExists':
 
2980
        raise errors.FileExists(err.error_args[0])
 
2981
    elif err.error_verb == 'DirectoryNotEmpty':
 
2982
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2983
    elif err.error_verb == 'ShortReadvError':
 
2984
        args = err.error_args
 
2985
        raise errors.ShortReadvError(
 
2986
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2987
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2988
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2989
        val = err.error_args[1]
 
2990
        start = int(err.error_args[2])
 
2991
        end = int(err.error_args[3])
 
2992
        reason = str(err.error_args[4]) # reason must always be a string
 
2993
        if val.startswith('u:'):
 
2994
            val = val[2:].decode('utf-8')
 
2995
        elif val.startswith('s:'):
 
2996
            val = val[2:].decode('base64')
 
2997
        if err.error_verb == 'UnicodeDecodeError':
 
2998
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2999
        elif err.error_verb == 'UnicodeEncodeError':
 
3000
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
3001
    elif err.error_verb == 'ReadOnlyError':
 
3002
        raise errors.TransportNotPossible('readonly transport')
 
3003
    raise errors.UnknownErrorFromSmartServer(err)