/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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