/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
 
    urlutils,
41
 
    )
42
 
from . import (
43
 
    branch as bzrbranch,
44
 
    bzrdir as _mod_bzrdir,
45
 
    inventory_delta,
46
 
    testament as _mod_testament,
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,
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 default_revision: For lines that don't match a basis, mark them
972
 
            with this revision id. Not all implementations will make use of
973
 
            this value.
974
 
        """
975
 
        ret = self._repository._annotate_file_revision(
976
 
            self.get_revision_id(), path, file_id=None,
977
 
            default_revision=default_revision)
978
 
        if ret is None:
979
 
            return super(RemoteInventoryTree, self).annotate_iter(
980
 
                path, 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 ReconcileResult
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 = ReconcileResult()
2490
 
            result.garbage_inventories = None
2491
 
            result.inconsistent_parents = None
2492
 
            result.aborted = None
2493
 
            for line in body.split(b'\n'):
2494
 
                if not line:
2495
 
                    continue
2496
 
                key, val_text = line.split(b':')
2497
 
                if key == b"garbage_inventories":
2498
 
                    result.garbage_inventories = int(val_text)
2499
 
                elif key == b"inconsistent_parents":
2500
 
                    result.inconsistent_parents = int(val_text)
2501
 
                else:
2502
 
                    mutter("unknown reconcile key %r" % key)
2503
 
            return result
 
1543
        self._ensure_real()
 
1544
        return self._real_repository.reconcile(other=other, thorough=thorough)
2504
1545
 
2505
1546
    def all_revision_ids(self):
2506
 
        path = self.controldir._path_for_remote_call(self._client)
2507
 
        try:
2508
 
            response_tuple, response_handler = self._call_expecting_body(
2509
 
                b"Repository.all_revision_ids", path)
2510
 
        except errors.UnknownSmartMethod:
2511
 
            self._ensure_real()
2512
 
            return self._real_repository.all_revision_ids()
2513
 
        if response_tuple != (b"ok", ):
2514
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2515
 
        revids = set(response_handler.read_body_bytes().splitlines())
2516
 
        for fallback in self._fallback_repositories:
2517
 
            revids.update(set(fallback.all_revision_ids()))
2518
 
        return list(revids)
2519
 
 
2520
 
    def _filtered_revision_trees(self, revision_ids, file_ids):
2521
 
        """Return Tree for a revision on this branch with only some files.
2522
 
 
2523
 
        :param revision_ids: a sequence of revision-ids;
2524
 
          a revision-id may not be None or b'null:'
2525
 
        :param file_ids: if not None, the result is filtered
2526
 
          so that only those file-ids, their parents and their
2527
 
          children are included.
2528
 
        """
2529
 
        inventories = self.iter_inventories(revision_ids)
2530
 
        for inv in inventories:
2531
 
            # Should we introduce a FilteredRevisionTree class rather
2532
 
            # than pre-filter the inventory here?
2533
 
            filtered_inv = inv.filter(file_ids)
2534
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2535
 
 
 
1547
        self._ensure_real()
 
1548
        return self._real_repository.all_revision_ids()
 
1549
 
 
1550
    @needs_read_lock
2536
1551
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2537
 
        with self.lock_read():
2538
 
            medium = self._client._medium
2539
 
            if medium._is_remote_before((1, 2)):
2540
 
                self._ensure_real()
2541
 
                for delta in self._real_repository.get_deltas_for_revisions(
2542
 
                        revisions, specific_fileids):
2543
 
                    yield delta
2544
 
                return
2545
 
            # Get the revision-ids of interest
2546
 
            required_trees = set()
2547
 
            for revision in revisions:
2548
 
                required_trees.add(revision.revision_id)
2549
 
                required_trees.update(revision.parent_ids[:1])
2550
 
 
2551
 
            # Get the matching filtered trees. Note that it's more
2552
 
            # efficient to pass filtered trees to changes_from() rather
2553
 
            # than doing the filtering afterwards. changes_from() could
2554
 
            # arguably do the filtering itself but it's path-based, not
2555
 
            # file-id based, so filtering before or afterwards is
2556
 
            # currently easier.
2557
 
            if specific_fileids is None:
2558
 
                trees = dict((t.get_revision_id(), t) for
2559
 
                             t in self.revision_trees(required_trees))
2560
 
            else:
2561
 
                trees = dict((t.get_revision_id(), t) for
2562
 
                             t in self._filtered_revision_trees(required_trees,
2563
 
                                                                specific_fileids))
2564
 
 
2565
 
            # Calculate the deltas
2566
 
            for revision in revisions:
2567
 
                if not revision.parent_ids:
2568
 
                    old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2569
 
                else:
2570
 
                    old_tree = trees[revision.parent_ids[0]]
2571
 
                yield trees[revision.revision_id].changes_from(old_tree)
2572
 
 
 
1552
        self._ensure_real()
 
1553
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1554
            specific_fileids=specific_fileids)
 
1555
 
 
1556
    @needs_read_lock
2573
1557
    def get_revision_delta(self, revision_id, specific_fileids=None):
2574
 
        with self.lock_read():
2575
 
            r = self.get_revision(revision_id)
2576
 
            return list(self.get_deltas_for_revisions([r],
2577
 
                                                      specific_fileids=specific_fileids))[0]
 
1558
        self._ensure_real()
 
1559
        return self._real_repository.get_revision_delta(revision_id,
 
1560
            specific_fileids=specific_fileids)
2578
1561
 
 
1562
    @needs_read_lock
2579
1563
    def revision_trees(self, revision_ids):
2580
 
        with self.lock_read():
2581
 
            inventories = self.iter_inventories(revision_ids)
2582
 
            for inv in inventories:
2583
 
                yield RemoteInventoryTree(self, inv, inv.revision_id)
 
1564
        self._ensure_real()
 
1565
        return self._real_repository.revision_trees(revision_ids)
2584
1566
 
 
1567
    @needs_read_lock
2585
1568
    def get_revision_reconcile(self, revision_id):
2586
 
        with self.lock_read():
2587
 
            self._ensure_real()
2588
 
            return self._real_repository.get_revision_reconcile(revision_id)
 
1569
        self._ensure_real()
 
1570
        return self._real_repository.get_revision_reconcile(revision_id)
2589
1571
 
 
1572
    @needs_read_lock
2590
1573
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2591
 
        with self.lock_read():
2592
 
            self._ensure_real()
2593
 
            return self._real_repository.check(revision_ids=revision_ids,
2594
 
                                               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)
2595
1577
 
2596
1578
    def copy_content_into(self, destination, revision_id=None):
2597
 
        """Make a complete copy of the content in self into destination.
2598
 
 
2599
 
        This is a destructive operation! Do not use it on existing
2600
 
        repositories.
2601
 
        """
2602
 
        interrepo = _mod_repository.InterRepository.get(self, destination)
2603
 
        return interrepo.copy_content(revision_id)
 
1579
        self._ensure_real()
 
1580
        return self._real_repository.copy_content_into(
 
1581
            destination, revision_id=revision_id)
2604
1582
 
2605
1583
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2606
1584
        # get a tarball of the remote repository, and copy from that into the
2607
1585
        # destination
 
1586
        from bzrlib import osutils
2608
1587
        import tarfile
2609
1588
        # TODO: Maybe a progress bar while streaming the tarball?
2610
 
        note(gettext("Copying repository content as tarball..."))
 
1589
        note("Copying repository content as tarball...")
2611
1590
        tar_file = self._get_tarball('bz2')
2612
1591
        if tar_file is None:
2613
1592
            return None
2614
1593
        destination = to_bzrdir.create_repository()
2615
1594
        try:
2616
1595
            tar = tarfile.open('repository', fileobj=tar_file,
2617
 
                               mode='r|bz2')
 
1596
                mode='r|bz2')
2618
1597
            tmpdir = osutils.mkdtemp()
2619
1598
            try:
2620
 
                tar.extractall(tmpdir)
2621
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
1599
                _extract_tar(tar, tmpdir)
 
1600
                tmp_bzrdir = BzrDir.open(tmpdir)
2622
1601
                tmp_repo = tmp_bzrdir.open_repository()
2623
1602
                tmp_repo.copy_content_into(destination, revision_id)
2624
1603
            finally:
2639
1618
        self._ensure_real()
2640
1619
        return self._real_repository.inventories
2641
1620
 
 
1621
    @needs_write_lock
2642
1622
    def pack(self, hint=None, clean_obsolete_packs=False):
2643
1623
        """Compress the data within the repository.
 
1624
 
 
1625
        This is not currently implemented within the smart server.
2644
1626
        """
2645
 
        if hint is None:
2646
 
            body = b""
2647
 
        else:
2648
 
            body = b"".join([l.encode('ascii') + b"\n" for l in hint])
2649
 
        with self.lock_write():
2650
 
            path = self.controldir._path_for_remote_call(self._client)
2651
 
            try:
2652
 
                response, handler = self._call_with_body_bytes_expecting_body(
2653
 
                    b'Repository.pack', (path, self._lock_token,
2654
 
                                         str(clean_obsolete_packs).encode('ascii')), body)
2655
 
            except errors.UnknownSmartMethod:
2656
 
                self._ensure_real()
2657
 
                return self._real_repository.pack(hint=hint,
2658
 
                                                  clean_obsolete_packs=clean_obsolete_packs)
2659
 
            handler.cancel_read_body()
2660
 
            if response != (b'ok', ):
2661
 
                raise errors.UnexpectedSmartServerResponse(response)
 
1627
        self._ensure_real()
 
1628
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
2662
1629
 
2663
1630
    @property
2664
1631
    def revisions(self):
2665
1632
        """Decorate the real repository for now.
2666
1633
 
 
1634
        In the short term this should become a real object to intercept graph
 
1635
        lookups.
 
1636
 
2667
1637
        In the long term a full blown network facility is needed.
2668
1638
        """
2669
1639
        self._ensure_real()
2671
1641
 
2672
1642
    def set_make_working_trees(self, new_value):
2673
1643
        if new_value:
2674
 
            new_value_str = b"True"
 
1644
            new_value_str = "True"
2675
1645
        else:
2676
 
            new_value_str = b"False"
2677
 
        path = self.controldir._path_for_remote_call(self._client)
 
1646
            new_value_str = "False"
 
1647
        path = self.bzrdir._path_for_remote_call(self._client)
2678
1648
        try:
2679
1649
            response = self._call(
2680
 
                b'Repository.set_make_working_trees', path, new_value_str)
 
1650
                'Repository.set_make_working_trees', path, new_value_str)
2681
1651
        except errors.UnknownSmartMethod:
2682
1652
            self._ensure_real()
2683
1653
            self._real_repository.set_make_working_trees(new_value)
2684
1654
        else:
2685
 
            if response[0] != b'ok':
 
1655
            if response[0] != 'ok':
2686
1656
                raise errors.UnexpectedSmartServerResponse(response)
2687
1657
 
2688
1658
    @property
2695
1665
        self._ensure_real()
2696
1666
        return self._real_repository.signatures
2697
1667
 
 
1668
    @needs_write_lock
2698
1669
    def sign_revision(self, revision_id, gpg_strategy):
2699
 
        with self.lock_write():
2700
 
            testament = _mod_testament.Testament.from_revision(
2701
 
                self, revision_id)
2702
 
            plaintext = testament.as_short_text()
2703
 
            self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1670
        self._ensure_real()
 
1671
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2704
1672
 
2705
1673
    @property
2706
1674
    def texts(self):
2712
1680
        self._ensure_real()
2713
1681
        return self._real_repository.texts
2714
1682
 
2715
 
    def _iter_revisions_rpc(self, revision_ids):
2716
 
        body = b"\n".join(revision_ids)
2717
 
        path = self.controldir._path_for_remote_call(self._client)
2718
 
        response_tuple, response_handler = (
2719
 
            self._call_with_body_bytes_expecting_body(
2720
 
                b"Repository.iter_revisions", (path, ), body))
2721
 
        if response_tuple[0] != b"ok":
2722
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2723
 
        serializer_format = response_tuple[1].decode('ascii')
2724
 
        serializer = serializer_format_registry.get(serializer_format)
2725
 
        byte_stream = response_handler.read_streamed_body()
2726
 
        decompressor = zlib.decompressobj()
2727
 
        chunks = []
2728
 
        for bytes in byte_stream:
2729
 
            chunks.append(decompressor.decompress(bytes))
2730
 
            if decompressor.unused_data != b"":
2731
 
                chunks.append(decompressor.flush())
2732
 
                yield serializer.read_revision_from_string(b"".join(chunks))
2733
 
                unused = decompressor.unused_data
2734
 
                decompressor = zlib.decompressobj()
2735
 
                chunks = [decompressor.decompress(unused)]
2736
 
        chunks.append(decompressor.flush())
2737
 
        text = b"".join(chunks)
2738
 
        if text != b"":
2739
 
            yield serializer.read_revision_from_string(b"".join(chunks))
2740
 
 
2741
 
    def iter_revisions(self, revision_ids):
2742
 
        for rev_id in revision_ids:
2743
 
            if not rev_id or not isinstance(rev_id, bytes):
2744
 
                raise errors.InvalidRevisionId(
2745
 
                    revision_id=rev_id, branch=self)
2746
 
        with self.lock_read():
2747
 
            try:
2748
 
                missing = set(revision_ids)
2749
 
                for rev in self._iter_revisions_rpc(revision_ids):
2750
 
                    missing.remove(rev.revision_id)
2751
 
                    yield (rev.revision_id, rev)
2752
 
                for fallback in self._fallback_repositories:
2753
 
                    if not missing:
2754
 
                        break
2755
 
                    for (revid, rev) in fallback.iter_revisions(missing):
2756
 
                        if rev is not None:
2757
 
                            yield (revid, rev)
2758
 
                            missing.remove(revid)
2759
 
                for revid in missing:
2760
 
                    yield (revid, None)
2761
 
            except errors.UnknownSmartMethod:
2762
 
                self._ensure_real()
2763
 
                for entry in self._real_repository.iter_revisions(revision_ids):
2764
 
                    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)
2765
1687
 
2766
1688
    def supports_rich_root(self):
2767
1689
        return self._format.rich_root_data
2768
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
 
2769
1695
    @property
2770
1696
    def _serializer(self):
2771
1697
        return self._format._serializer
2772
1698
 
2773
1699
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2774
 
        with self.lock_write():
2775
 
            signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
2776
 
            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)
2777
1703
 
2778
1704
    def add_signature_text(self, revision_id, signature):
2779
 
        if self._real_repository:
2780
 
            # If there is a real repository the write group will
2781
 
            # be in the real repository as well, so use that:
2782
 
            self._ensure_real()
2783
 
            return self._real_repository.add_signature_text(
2784
 
                revision_id, signature)
2785
 
        path = self.controldir._path_for_remote_call(self._client)
2786
 
        response, handler = self._call_with_body_bytes_expecting_body(
2787
 
            b'Repository.add_signature_text', (path, self._lock_token,
2788
 
                                               revision_id) +
2789
 
            tuple([token.encode('utf-8')
2790
 
                   for token in self._write_group_tokens]),
2791
 
            signature)
2792
 
        handler.cancel_read_body()
2793
 
        self.refresh_data()
2794
 
        if response[0] != b'ok':
2795
 
            raise errors.UnexpectedSmartServerResponse(response)
2796
 
        self._write_group_tokens = [token.decode(
2797
 
            'utf-8') for token in response[1:]]
 
1705
        self._ensure_real()
 
1706
        return self._real_repository.add_signature_text(revision_id, signature)
2798
1707
 
2799
1708
    def has_signature_for_revision_id(self, revision_id):
2800
 
        path = self.controldir._path_for_remote_call(self._client)
2801
 
        try:
2802
 
            response = self._call(b'Repository.has_signature_for_revision_id',
2803
 
                                  path, revision_id)
2804
 
        except errors.UnknownSmartMethod:
2805
 
            self._ensure_real()
2806
 
            return self._real_repository.has_signature_for_revision_id(
2807
 
                revision_id)
2808
 
        if response[0] not in (b'yes', b'no'):
2809
 
            raise SmartProtocolError(
2810
 
                'unexpected response code %s' % (response,))
2811
 
        if response[0] == b'yes':
2812
 
            return True
2813
 
        for fallback in self._fallback_repositories:
2814
 
            if fallback.has_signature_for_revision_id(revision_id):
2815
 
                return True
2816
 
        return False
2817
 
 
2818
 
    def verify_revision_signature(self, revision_id, gpg_strategy):
2819
 
        with self.lock_read():
2820
 
            if not self.has_signature_for_revision_id(revision_id):
2821
 
                return gpg.SIGNATURE_NOT_SIGNED, None
2822
 
            signature = self.get_signature_text(revision_id)
2823
 
 
2824
 
            testament = _mod_testament.Testament.from_revision(
2825
 
                self, revision_id)
2826
 
 
2827
 
            (status, key, signed_plaintext) = gpg_strategy.verify(signature)
2828
 
            if testament.as_short_text() != signed_plaintext:
2829
 
                return gpg.SIGNATURE_NOT_VALID, None
2830
 
            return (status, key)
 
1709
        self._ensure_real()
 
1710
        return self._real_repository.has_signature_for_revision_id(revision_id)
2831
1711
 
2832
1712
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2833
1713
        self._ensure_real()
2834
1714
        return self._real_repository.item_keys_introduced_by(revision_ids,
2835
 
                                                             _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()
2836
1721
 
2837
1722
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2838
1723
        self._ensure_real()
2847
1732
        providers = [self._unstacked_provider]
2848
1733
        if other is not None:
2849
1734
            providers.insert(0, other)
2850
 
        return graph.StackedParentsProvider(_LazyListJoin(
2851
 
            providers, self._fallback_repositories))
 
1735
        providers.extend(r._make_parents_provider() for r in
 
1736
                         self._fallback_repositories)
 
1737
        return graph.StackedParentsProvider(providers)
2852
1738
 
2853
1739
    def _serialise_search_recipe(self, recipe):
2854
1740
        """Serialise a graph search recipe.
2856
1742
        :param recipe: A search recipe (start, stop, count).
2857
1743
        :return: Serialised bytes.
2858
1744
        """
2859
 
        start_keys = b' '.join(recipe[1])
2860
 
        stop_keys = b' '.join(recipe[2])
2861
 
        count = str(recipe[3]).encode('ascii')
2862
 
        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))
2863
1749
 
2864
1750
    def _serialise_search_result(self, search_result):
2865
 
        parts = search_result.get_network_struct()
2866
 
        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)
2867
1758
 
2868
1759
    def autopack(self):
2869
 
        path = self.controldir._path_for_remote_call(self._client)
 
1760
        path = self.bzrdir._path_for_remote_call(self._client)
2870
1761
        try:
2871
 
            response = self._call(b'PackRepository.autopack', path)
 
1762
            response = self._call('PackRepository.autopack', path)
2872
1763
        except errors.UnknownSmartMethod:
2873
1764
            self._ensure_real()
2874
1765
            self._real_repository._pack_collection.autopack()
2875
1766
            return
2876
1767
        self.refresh_data()
2877
 
        if response[0] != b'ok':
2878
 
            raise errors.UnexpectedSmartServerResponse(response)
2879
 
 
2880
 
    def _revision_archive(self, revision_id, format, name, root, subdir,
2881
 
                          force_mtime=None):
2882
 
        path = self.controldir._path_for_remote_call(self._client)
2883
 
        format = format or ''
2884
 
        root = root or ''
2885
 
        subdir = subdir or ''
2886
 
        force_mtime = int(force_mtime) if force_mtime is not None else None
2887
 
        try:
2888
 
            response, protocol = self._call_expecting_body(
2889
 
                b'Repository.revision_archive', path,
2890
 
                revision_id,
2891
 
                format.encode('ascii'),
2892
 
                os.path.basename(name).encode('utf-8'),
2893
 
                root.encode('utf-8'),
2894
 
                subdir.encode('utf-8'),
2895
 
                force_mtime)
2896
 
        except errors.UnknownSmartMethod:
2897
 
            return None
2898
 
        if response[0] == b'ok':
2899
 
            return iter([protocol.read_body_bytes()])
2900
 
        raise errors.UnexpectedSmartServerResponse(response)
2901
 
 
2902
 
    def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
2903
 
        path = self.controldir._path_for_remote_call(self._client)
2904
 
        tree_path = tree_path.encode('utf-8')
2905
 
        file_id = file_id or b''
2906
 
        default_revision = default_revision or b''
2907
 
        try:
2908
 
            response, handler = self._call_expecting_body(
2909
 
                b'Repository.annotate_file_revision', path,
2910
 
                revid, tree_path, file_id, default_revision)
2911
 
        except errors.UnknownSmartMethod:
2912
 
            return None
2913
 
        if response[0] != b'ok':
2914
 
            raise errors.UnexpectedSmartServerResponse(response)
2915
 
        return map(tuple, bencode.bdecode(handler.read_body_bytes()))
2916
 
 
2917
 
 
2918
 
class RemoteStreamSink(vf_repository.StreamSink):
 
1768
        if response[0] != 'ok':
 
1769
            raise errors.UnexpectedSmartServerResponse(response)
 
1770
 
 
1771
 
 
1772
class RemoteStreamSink(repository.StreamSink):
2919
1773
 
2920
1774
    def _insert_real(self, stream, src_format, resume_tokens):
2921
1775
        self.target_repo._ensure_real()
2925
1779
            self.target_repo.autopack()
2926
1780
        return result
2927
1781
 
2928
 
    def insert_missing_keys(self, source, missing_keys):
2929
 
        if (isinstance(source, RemoteStreamSource)
2930
 
                and source.from_repository._client._medium == self.target_repo._client._medium):
2931
 
            # Streaming from and to the same medium is tricky, since we don't support
2932
 
            # more than one concurrent request. For now, just force VFS.
2933
 
            stream = source._get_real_stream_for_missing_keys(missing_keys)
2934
 
        else:
2935
 
            stream = source.get_stream_for_missing_keys(missing_keys)
2936
 
        return self.insert_stream_without_locking(stream,
2937
 
                                                  self.target_repo._format)
2938
 
 
2939
1782
    def insert_stream(self, stream, src_format, resume_tokens):
2940
1783
        target = self.target_repo
2941
1784
        target._unstacked_provider.missing_keys.clear()
2942
 
        candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
 
1785
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2943
1786
        if target._lock_token:
2944
 
            candidate_calls.append(
2945
 
                (b'Repository.insert_stream_locked', (1, 14)))
2946
 
            lock_args = (target._lock_token or b'',)
 
1787
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1788
            lock_args = (target._lock_token or '',)
2947
1789
        else:
2948
 
            candidate_calls.append((b'Repository.insert_stream', (1, 13)))
 
1790
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
2949
1791
            lock_args = ()
2950
1792
        client = target._client
2951
1793
        medium = client._medium
2952
 
        path = target.controldir._path_for_remote_call(client)
 
1794
        path = target.bzrdir._path_for_remote_call(client)
2953
1795
        # Probe for the verb to use with an empty stream before sending the
2954
1796
        # real stream to it.  We do this both to avoid the risk of sending a
2955
1797
        # large request that is then rejected, and because we don't want to
2966
1808
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2967
1809
            try:
2968
1810
                response = client.call_with_body_stream(
2969
 
                    (verb, path, b'') + lock_args, byte_stream)
 
1811
                    (verb, path, '') + lock_args, byte_stream)
2970
1812
            except errors.UnknownSmartMethod:
2971
1813
                medium._remember_remote_is_before(required_version)
2972
1814
            else:
2985
1827
            stream = self._stop_stream_if_inventory_delta(stream)
2986
1828
        byte_stream = smart_repo._stream_to_byte_stream(
2987
1829
            stream, src_format)
2988
 
        resume_tokens = b' '.join([token.encode('utf-8')
2989
 
                                   for token in resume_tokens])
 
1830
        resume_tokens = ' '.join(resume_tokens)
2990
1831
        response = client.call_with_body_stream(
2991
1832
            (verb, path, resume_tokens) + lock_args, byte_stream)
2992
 
        if response[0][0] not in (b'ok', b'missing-basis'):
 
1833
        if response[0][0] not in ('ok', 'missing-basis'):
2993
1834
            raise errors.UnexpectedSmartServerResponse(response)
2994
1835
        if self._last_substream is not None:
2995
1836
            # The stream included an inventory-delta record, but the remote
2997
1838
            # rest of the stream via VFS.
2998
1839
            self.target_repo.refresh_data()
2999
1840
            return self._resume_stream_with_vfs(response, src_format)
3000
 
        if response[0][0] == b'missing-basis':
 
1841
        if response[0][0] == 'missing-basis':
3001
1842
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3002
 
            resume_tokens = [token.decode('utf-8') for token in tokens]
3003
 
            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)
3004
1845
        else:
3005
1846
            self.target_repo.refresh_data()
3006
1847
            return [], set()
3009
1850
        """Resume sending a stream via VFS, first resending the record and
3010
1851
        substream that couldn't be sent via an insert_stream verb.
3011
1852
        """
3012
 
        if response[0][0] == b'missing-basis':
 
1853
        if response[0][0] == 'missing-basis':
3013
1854
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3014
 
            tokens = [token.decode('utf-8') for token in tokens]
3015
1855
            # Ignore missing_keys, we haven't finished inserting yet
3016
1856
        else:
3017
1857
            tokens = []
3018
 
 
3019
1858
        def resume_substream():
3020
1859
            # Yield the substream that was interrupted.
3021
1860
            for record in self._last_substream:
3022
1861
                yield record
3023
1862
            self._last_substream = None
3024
 
 
3025
1863
        def resume_stream():
3026
1864
            # Finish sending the interrupted substream
3027
1865
            yield ('inventory-deltas', resume_substream())
3038
1876
        self._last_substream and self._last_stream so that the stream can be
3039
1877
        resumed by _resume_stream_with_vfs.
3040
1878
        """
3041
 
 
 
1879
                    
3042
1880
        stream_iter = iter(stream)
3043
1881
        for substream_kind, substream in stream_iter:
3044
1882
            if substream_kind == 'inventory-deltas':
3047
1885
                return
3048
1886
            else:
3049
1887
                yield substream_kind, substream
3050
 
 
3051
 
 
3052
 
class RemoteStreamSource(vf_repository.StreamSource):
 
1888
            
 
1889
 
 
1890
class RemoteStreamSource(repository.StreamSource):
3053
1891
    """Stream data from a remote server."""
3054
1892
 
3055
1893
    def get_stream(self, search):
3056
 
        if (self.from_repository._fallback_repositories
3057
 
                and self.to_format._fetch_order == 'topological'):
 
1894
        if (self.from_repository._fallback_repositories and
 
1895
            self.to_format._fetch_order == 'topological'):
3058
1896
            return self._real_stream(self.from_repository, search)
3059
1897
        sources = []
3060
1898
        seen = set()
3068
1906
            sources.append(repo)
3069
1907
        return self.missing_parents_chain(search, sources)
3070
1908
 
3071
 
    def _get_real_stream_for_missing_keys(self, missing_keys):
 
1909
    def get_stream_for_missing_keys(self, missing_keys):
3072
1910
        self.from_repository._ensure_real()
3073
1911
        real_repo = self.from_repository._real_repository
3074
1912
        real_source = real_repo._get_source(self.to_format)
3075
1913
        return real_source.get_stream_for_missing_keys(missing_keys)
3076
1914
 
3077
 
    def get_stream_for_missing_keys(self, missing_keys):
3078
 
        if not isinstance(self.from_repository, RemoteRepository):
3079
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3080
 
        client = self.from_repository._client
3081
 
        medium = client._medium
3082
 
        if medium._is_remote_before((3, 0)):
3083
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3084
 
        path = self.from_repository.controldir._path_for_remote_call(client)
3085
 
        args = (path, self.to_format.network_name())
3086
 
        search_bytes = b'\n'.join(
3087
 
            [b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
3088
 
        try:
3089
 
            response, handler = self.from_repository._call_with_body_bytes_expecting_body(
3090
 
                b'Repository.get_stream_for_missing_keys', args, search_bytes)
3091
 
        except (errors.UnknownSmartMethod, errors.UnknownFormatError):
3092
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3093
 
        if response[0] != b'ok':
3094
 
            raise errors.UnexpectedSmartServerResponse(response)
3095
 
        byte_stream = handler.read_streamed_body()
3096
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3097
 
                                                               self._record_counter)
3098
 
        if src_format.network_name() != self.from_repository._format.network_name():
3099
 
            raise AssertionError(
3100
 
                "Mismatched RemoteRepository and stream src %r, %r" % (
3101
 
                    src_format.network_name(), repo._format.network_name()))
3102
 
        return stream
3103
 
 
3104
1915
    def _real_stream(self, repo, search):
3105
1916
        """Get a stream for search from repo.
3106
 
 
3107
 
        This never called RemoteStreamSource.get_stream, and is a helper
3108
 
        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 
3109
1920
        reliably whether fallback back because of old servers or trying
3110
1921
        to stream from a non-RemoteRepository (which the stacked support
3111
1922
        code will do).
3136
1947
            return self._real_stream(repo, search)
3137
1948
        client = repo._client
3138
1949
        medium = client._medium
3139
 
        path = repo.controldir._path_for_remote_call(client)
 
1950
        path = repo.bzrdir._path_for_remote_call(client)
3140
1951
        search_bytes = repo._serialise_search_result(search)
3141
1952
        args = (path, self.to_format.network_name())
3142
1953
        candidate_verbs = [
3143
 
            (b'Repository.get_stream_1.19', (1, 19)),
3144
 
            (b'Repository.get_stream', (1, 13))]
3145
 
 
 
1954
            ('Repository.get_stream_1.19', (1, 19)),
 
1955
            ('Repository.get_stream', (1, 13))]
3146
1956
        found_verb = False
3147
1957
        for verb, version in candidate_verbs:
3148
1958
            if medium._is_remote_before(version):
3152
1962
                    verb, args, search_bytes)
3153
1963
            except errors.UnknownSmartMethod:
3154
1964
                medium._remember_remote_is_before(version)
3155
 
            except errors.UnknownErrorFromSmartServer as e:
3156
 
                if isinstance(search, vf_search.EverythingResult):
3157
 
                    error_verb = e.error_from_smart_server.error_verb
3158
 
                    if error_verb == b'BadSearch':
3159
 
                        # Pre-2.4 servers don't support this sort of search.
3160
 
                        # XXX: perhaps falling back to VFS on BadSearch is a
3161
 
                        # good idea in general?  It might provide a little bit
3162
 
                        # of protection against client-side bugs.
3163
 
                        medium._remember_remote_is_before((2, 4))
3164
 
                        break
3165
 
                raise
3166
1965
            else:
3167
1966
                response_tuple, response_handler = response
3168
1967
                found_verb = True
3169
1968
                break
3170
1969
        if not found_verb:
3171
1970
            return self._real_stream(repo, search)
3172
 
        if response_tuple[0] != b'ok':
 
1971
        if response_tuple[0] != 'ok':
3173
1972
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3174
1973
        byte_stream = response_handler.read_streamed_body()
3175
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3176
 
                                                               self._record_counter)
 
1974
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
3177
1975
        if src_format.network_name() != repo._format.network_name():
3178
1976
            raise AssertionError(
3179
1977
                "Mismatched RemoteRepository and stream src %r, %r" % (
3180
 
                    src_format.network_name(), repo._format.network_name()))
 
1978
                src_format.network_name(), repo._format.network_name()))
3181
1979
        return stream
3182
1980
 
3183
1981
    def missing_parents_chain(self, search, sources):
3223
2021
    """
3224
2022
 
3225
2023
    def __init__(self, bzrdir, _client):
3226
 
        self.controldir = bzrdir
 
2024
        self.bzrdir = bzrdir
3227
2025
        self._client = _client
3228
2026
        self._need_find_modes = True
3229
2027
        LockableFiles.__init__(
3240
2038
 
3241
2039
    def __init__(self, network_name=None):
3242
2040
        super(RemoteBranchFormat, self).__init__()
3243
 
        self._matchingcontroldir = RemoteBzrDirFormat()
3244
 
        self._matchingcontroldir.set_branch_format(self)
 
2041
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2042
        self._matchingbzrdir.set_branch_format(self)
3245
2043
        self._custom_format = None
3246
2044
        self._network_name = network_name
3247
2045
 
3248
2046
    def __eq__(self, other):
3249
 
        return (isinstance(other, RemoteBranchFormat)
3250
 
                and self.__dict__ == other.__dict__)
 
2047
        return (isinstance(other, RemoteBranchFormat) and
 
2048
            self.__dict__ == other.__dict__)
3251
2049
 
3252
2050
    def _ensure_real(self):
3253
2051
        if self._custom_format is None:
3254
 
            try:
3255
 
                self._custom_format = branch.network_format_registry.get(
3256
 
                    self._network_name)
3257
 
            except KeyError:
3258
 
                raise errors.UnknownFormatError(kind='branch',
3259
 
                                                format=self._network_name)
 
2052
            self._custom_format = branch.network_format_registry.get(
 
2053
                self._network_name)
3260
2054
 
3261
2055
    def get_format_description(self):
3262
2056
        self._ensure_real()
3265
2059
    def network_name(self):
3266
2060
        return self._network_name
3267
2061
 
3268
 
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
3269
 
        return a_controldir.open_branch(name=name,
3270
 
                                        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)
3271
2065
 
3272
 
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3273
 
                        repository=None):
 
2066
    def _vfs_initialize(self, a_bzrdir, name):
3274
2067
        # Initialisation when using a local bzrdir object, or a non-vfs init
3275
2068
        # method is not available on the server.
3276
2069
        # self._custom_format is always set - the start of initialize ensures
3277
2070
        # that.
3278
 
        if isinstance(a_controldir, RemoteBzrDir):
3279
 
            a_controldir._ensure_real()
3280
 
            result = self._custom_format.initialize(a_controldir._real_bzrdir,
3281
 
                                                    name=name, append_revisions_only=append_revisions_only,
3282
 
                                                    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)
3283
2075
        else:
3284
2076
            # We assume the bzrdir is parameterised; it may not be.
3285
 
            result = self._custom_format.initialize(a_controldir, name=name,
3286
 
                                                    append_revisions_only=append_revisions_only,
3287
 
                                                    repository=repository)
3288
 
        if (isinstance(a_controldir, RemoteBzrDir)
3289
 
                and not isinstance(result, RemoteBranch)):
3290
 
            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,
3291
2081
                                  name=name)
3292
2082
        return result
3293
2083
 
3294
 
    def initialize(self, a_controldir, name=None, repository=None,
3295
 
                   append_revisions_only=None):
3296
 
        if name is None:
3297
 
            name = a_controldir._get_selected_branch()
 
2084
    def initialize(self, a_bzrdir, name=None):
3298
2085
        # 1) get the network name to use.
3299
2086
        if self._custom_format:
3300
2087
            network_name = self._custom_format.network_name()
3301
2088
        else:
3302
 
            # Select the current breezy default and ask for that.
3303
 
            reference_bzrdir_format = controldir.format_registry.get(
3304
 
                'default')()
 
2089
            # Select the current bzrlib default and ask for that.
 
2090
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
3305
2091
            reference_format = reference_bzrdir_format.get_branch_format()
3306
2092
            self._custom_format = reference_format
3307
2093
            network_name = reference_format.network_name()
3308
2094
        # Being asked to create on a non RemoteBzrDir:
3309
 
        if not isinstance(a_controldir, RemoteBzrDir):
3310
 
            return self._vfs_initialize(a_controldir, name=name,
3311
 
                                        append_revisions_only=append_revisions_only,
3312
 
                                        repository=repository)
3313
 
        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
3314
2098
        if medium._is_remote_before((1, 13)):
3315
 
            return self._vfs_initialize(a_controldir, name=name,
3316
 
                                        append_revisions_only=append_revisions_only,
3317
 
                                        repository=repository)
 
2099
            return self._vfs_initialize(a_bzrdir, name=name)
3318
2100
        # Creating on a remote bzr dir.
3319
2101
        # 2) try direct creation via RPC
3320
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
3321
 
        if name != "":
 
2102
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2103
        if name is not None:
3322
2104
            # XXX JRV20100304: Support creating colocated branches
3323
2105
            raise errors.NoColocatedBranchSupport(self)
3324
 
        verb = b'BzrDir.create_branch'
 
2106
        verb = 'BzrDir.create_branch'
3325
2107
        try:
3326
 
            response = a_controldir._call(verb, path, network_name)
 
2108
            response = a_bzrdir._call(verb, path, network_name)
3327
2109
        except errors.UnknownSmartMethod:
3328
2110
            # Fallback - use vfs methods
3329
2111
            medium._remember_remote_is_before((1, 13))
3330
 
            return self._vfs_initialize(a_controldir, name=name,
3331
 
                                        append_revisions_only=append_revisions_only,
3332
 
                                        repository=repository)
3333
 
        if response[0] != b'ok':
 
2112
            return self._vfs_initialize(a_bzrdir, name=name)
 
2113
        if response[0] != 'ok':
3334
2114
            raise errors.UnexpectedSmartServerResponse(response)
3335
2115
        # Turn the response into a RemoteRepository object.
3336
2116
        format = RemoteBranchFormat(network_name=response[1])
3337
2117
        repo_format = response_tuple_to_repo_format(response[3:])
3338
 
        repo_path = response[2].decode('utf-8')
3339
 
        if repository is not None:
3340
 
            remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3341
 
            url_diff = urlutils.relative_url(repository.user_url,
3342
 
                                             remote_repo_url)
3343
 
            if url_diff != '.':
3344
 
                raise AssertionError(
3345
 
                    'repository.user_url %r does not match URL from server '
3346
 
                    'response (%r + %r)'
3347
 
                    % (repository.user_url, a_controldir.user_url, repo_path))
3348
 
            remote_repo = repository
 
2118
        if response[2] == '':
 
2119
            repo_bzrdir = a_bzrdir
3349
2120
        else:
3350
 
            if repo_path == '':
3351
 
                repo_bzrdir = a_controldir
3352
 
            else:
3353
 
                repo_bzrdir = RemoteBzrDir(
3354
 
                    a_controldir.root_transport.clone(
3355
 
                        repo_path), a_controldir._format,
3356
 
                    a_controldir._client)
3357
 
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3358
 
        remote_branch = RemoteBranch(a_controldir, remote_repo,
3359
 
                                     format=format, setup_stacking=False, name=name)
3360
 
        if append_revisions_only:
3361
 
            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)
3362
2127
        # XXX: We know this is a new branch, so it must have revno 0, revid
3363
2128
        # NULL_REVISION. Creating the branch locked would make this be unable
3364
2129
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3383
2148
        self._ensure_real()
3384
2149
        return self._custom_format.supports_set_append_revisions_only()
3385
2150
 
3386
 
    def _use_default_local_heads_to_fetch(self):
3387
 
        # If the branch format is a metadir format *and* its heads_to_fetch
3388
 
        # implementation is not overridden vs the base class, we can use the
3389
 
        # base class logic rather than use the heads_to_fetch RPC.  This is
3390
 
        # usually cheaper in terms of net round trips, as the last-revision and
3391
 
        # tags info fetched is cached and would be fetched anyway.
3392
 
        self._ensure_real()
3393
 
        if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3394
 
            branch_class = self._custom_format._branch_class()
3395
 
            heads_to_fetch_impl = get_unbound_function(
3396
 
                branch_class.heads_to_fetch)
3397
 
            if heads_to_fetch_impl is get_unbound_function(branch.Branch.heads_to_fetch):
3398
 
                return True
3399
 
        return False
3400
 
 
3401
 
 
3402
 
class RemoteBranchStore(_mod_config.IniFileStore):
3403
 
    """Branch store which attempts to use HPSS calls to retrieve branch store.
3404
 
 
3405
 
    Note that this is specific to bzr-based formats.
3406
 
    """
3407
 
 
3408
 
    def __init__(self, branch):
3409
 
        super(RemoteBranchStore, self).__init__()
3410
 
        self.branch = branch
3411
 
        self.id = "branch"
3412
 
        self._real_store = None
3413
 
 
3414
 
    def external_url(self):
3415
 
        return urlutils.join(self.branch.user_url, 'branch.conf')
3416
 
 
3417
 
    def _load_content(self):
3418
 
        path = self.branch._remote_path()
3419
 
        try:
3420
 
            response, handler = self.branch._call_expecting_body(
3421
 
                b'Branch.get_config_file', path)
3422
 
        except errors.UnknownSmartMethod:
3423
 
            self._ensure_real()
3424
 
            return self._real_store._load_content()
3425
 
        if len(response) and response[0] != b'ok':
3426
 
            raise errors.UnexpectedSmartServerResponse(response)
3427
 
        return handler.read_body_bytes()
3428
 
 
3429
 
    def _save_content(self, content):
3430
 
        path = self.branch._remote_path()
3431
 
        try:
3432
 
            response, handler = self.branch._call_with_body_bytes_expecting_body(
3433
 
                b'Branch.put_config_file', (path,
3434
 
                                            self.branch._lock_token, self.branch._repo_lock_token),
3435
 
                content)
3436
 
        except errors.UnknownSmartMethod:
3437
 
            self._ensure_real()
3438
 
            return self._real_store._save_content(content)
3439
 
        handler.cancel_read_body()
3440
 
        if response != (b'ok', ):
3441
 
            raise errors.UnexpectedSmartServerResponse(response)
3442
 
 
3443
 
    def _ensure_real(self):
3444
 
        self.branch._ensure_real()
3445
 
        if self._real_store is None:
3446
 
            self._real_store = _mod_config.BranchStore(self.branch)
3447
 
 
3448
2151
 
3449
2152
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3450
2153
    """Branch stored on a server accessed by HPSS RPC.
3453
2156
    """
3454
2157
 
3455
2158
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3456
 
                 _client=None, format=None, setup_stacking=True, name=None,
3457
 
                 possible_transports=None):
 
2159
        _client=None, format=None, setup_stacking=True, name=None):
3458
2160
        """Create a RemoteBranch instance.
3459
2161
 
3460
2162
        :param real_branch: An optional local implementation of the branch
3471
2173
        # We intentionally don't call the parent class's __init__, because it
3472
2174
        # will try to assign to self.tags, which is a property in this subclass.
3473
2175
        # And the parent's __init__ doesn't do much anyway.
3474
 
        self.controldir = remote_bzrdir
3475
 
        self.name = name
 
2176
        self.bzrdir = remote_bzrdir
3476
2177
        if _client is not None:
3477
2178
            self._client = _client
3478
2179
        else:
3490
2191
            self._real_branch.repository = self.repository
3491
2192
        else:
3492
2193
            self._real_branch = None
3493
 
        # Fill out expected attributes of branch for breezy API users.
 
2194
        # Fill out expected attributes of branch for bzrlib API users.
3494
2195
        self._clear_cached_state()
3495
2196
        # TODO: deprecate self.base in favor of user_url
3496
 
        self.base = self.controldir.user_url
 
2197
        self.base = self.bzrdir.user_url
3497
2198
        self._name = name
3498
2199
        self._control_files = None
3499
2200
        self._lock_mode = None
3501
2202
        self._repo_lock_token = None
3502
2203
        self._lock_count = 0
3503
2204
        self._leave_lock = False
3504
 
        self.conf_store = None
3505
2205
        # Setup a format: note that we cannot call _ensure_real until all the
3506
2206
        # attributes above are set: This code cannot be moved higher up in this
3507
2207
        # function.
3527
2227
            hook(self)
3528
2228
        self._is_stacked = False
3529
2229
        if setup_stacking:
3530
 
            self._setup_stacking(possible_transports)
 
2230
            self._setup_stacking()
3531
2231
 
3532
 
    def _setup_stacking(self, possible_transports):
 
2232
    def _setup_stacking(self):
3533
2233
        # configure stacking into the remote repository, by reading it from
3534
2234
        # the vfs branch.
3535
2235
        try:
3536
2236
            fallback_url = self.get_stacked_on_url()
3537
 
        except (errors.NotStacked, branch.UnstackableBranchFormat,
3538
 
                errors.UnstackableRepositoryFormat) as e:
 
2237
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2238
            errors.UnstackableRepositoryFormat), e:
3539
2239
            return
3540
2240
        self._is_stacked = True
3541
 
        if possible_transports is None:
3542
 
            possible_transports = []
3543
 
        else:
3544
 
            possible_transports = list(possible_transports)
3545
 
        possible_transports.append(self.controldir.root_transport)
3546
 
        self._activate_fallback_location(fallback_url,
3547
 
                                         possible_transports=possible_transports)
 
2241
        self._activate_fallback_location(fallback_url)
3548
2242
 
3549
2243
    def _get_config(self):
3550
2244
        return RemoteBranchConfig(self)
3551
2245
 
3552
 
    def _get_config_store(self):
3553
 
        if self.conf_store is None:
3554
 
            self.conf_store = RemoteBranchStore(self)
3555
 
        return self.conf_store
3556
 
 
3557
 
    def store_uncommitted(self, creator):
3558
 
        self._ensure_real()
3559
 
        return self._real_branch.store_uncommitted(creator)
3560
 
 
3561
 
    def get_unshelver(self, tree):
3562
 
        self._ensure_real()
3563
 
        return self._real_branch.get_unshelver(tree)
3564
 
 
3565
2246
    def _get_real_transport(self):
3566
2247
        # if we try vfs access, return the real branch's vfs transport
3567
2248
        self._ensure_real()
3582
2263
        if self._real_branch is None:
3583
2264
            if not vfs.vfs_enabled():
3584
2265
                raise AssertionError('smart server vfs must be enabled '
3585
 
                                     'to use vfs implementation')
3586
 
            self.controldir._ensure_real()
3587
 
            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(
3588
2269
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3589
 
            # The remote branch and the real branch shares the same store. If
3590
 
            # we don't, there will always be cases where one of the stores
3591
 
            # doesn't see an update made on the other.
3592
 
            self._real_branch.conf_store = self.conf_store
3593
2270
            if self.repository._real_repository is None:
3594
2271
                # Give the remote repository the matching real repo.
3595
2272
                real_repo = self._real_branch.repository
3610
2287
 
3611
2288
    def _clear_cached_state(self):
3612
2289
        super(RemoteBranch, self)._clear_cached_state()
3613
 
        self._tags_bytes = None
3614
2290
        if self._real_branch is not None:
3615
2291
            self._real_branch._clear_cached_state()
3616
2292
 
3632
2308
        # because it triggers an _ensure_real that we otherwise might not need.
3633
2309
        if self._control_files is None:
3634
2310
            self._control_files = RemoteBranchLockableFiles(
3635
 
                self.controldir, self._client)
 
2311
                self.bzrdir, self._client)
3636
2312
        return self._control_files
3637
2313
 
 
2314
    def _get_checkout_format(self):
 
2315
        self._ensure_real()
 
2316
        return self._real_branch._get_checkout_format()
 
2317
 
3638
2318
    def get_physical_lock_status(self):
3639
2319
        """See Branch.get_physical_lock_status()."""
3640
 
        try:
3641
 
            response = self._client.call(b'Branch.get_physical_lock_status',
3642
 
                                         self._remote_path())
3643
 
        except errors.UnknownSmartMethod:
3644
 
            self._ensure_real()
3645
 
            return self._real_branch.get_physical_lock_status()
3646
 
        if response[0] not in (b'yes', b'no'):
3647
 
            raise errors.UnexpectedSmartServerResponse(response)
3648
 
        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()
3649
2323
 
3650
2324
    def get_stacked_on_url(self):
3651
2325
        """Get the URL this branch is stacked against.
3659
2333
        try:
3660
2334
            # there may not be a repository yet, so we can't use
3661
2335
            # self._translate_error, so we can't use self._call either.
3662
 
            response = self._client.call(b'Branch.get_stacked_on_url',
3663
 
                                         self._remote_path())
3664
 
        except errors.ErrorFromSmartServer as err:
 
2336
            response = self._client.call('Branch.get_stacked_on_url',
 
2337
                self._remote_path())
 
2338
        except errors.ErrorFromSmartServer, err:
3665
2339
            # there may not be a repository yet, so we can't call through
3666
2340
            # its _translate_error
3667
2341
            _translate_error(err, branch=self)
3668
 
        except errors.UnknownSmartMethod as err:
 
2342
        except errors.UnknownSmartMethod, err:
3669
2343
            self._ensure_real()
3670
2344
            return self._real_branch.get_stacked_on_url()
3671
 
        if response[0] != b'ok':
 
2345
        if response[0] != 'ok':
3672
2346
            raise errors.UnexpectedSmartServerResponse(response)
3673
 
        if sys.version_info[0] == 3:
3674
 
            return response[1].decode('utf-8')
3675
2347
        return response[1]
3676
2348
 
3677
2349
    def set_stacked_on_url(self, url):
3678
2350
        branch.Branch.set_stacked_on_url(self, url)
3679
 
        # We need the stacked_on_url to be visible both locally (to not query
3680
 
        # it repeatedly) and remotely (so smart verbs can get it server side)
3681
 
        # Without the following line,
3682
 
        # breezy.tests.per_branch.test_create_clone.TestCreateClone
3683
 
        # .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3684
 
        # fails for remote branches -- vila 2012-01-04
3685
 
        self.conf_store.save_changes()
3686
2351
        if not url:
3687
2352
            self._is_stacked = False
3688
2353
        else:
3689
2354
            self._is_stacked = True
3690
 
 
 
2355
        
3691
2356
    def _vfs_get_tags_bytes(self):
3692
2357
        self._ensure_real()
3693
2358
        return self._real_branch._get_tags_bytes()
3694
2359
 
3695
2360
    def _get_tags_bytes(self):
3696
 
        with self.lock_read():
3697
 
            if self._tags_bytes is None:
3698
 
                self._tags_bytes = self._get_tags_bytes_via_hpss()
3699
 
            return self._tags_bytes
3700
 
 
3701
 
    def _get_tags_bytes_via_hpss(self):
3702
2361
        medium = self._client._medium
3703
2362
        if medium._is_remote_before((1, 13)):
3704
2363
            return self._vfs_get_tags_bytes()
3705
2364
        try:
3706
 
            response = self._call(
3707
 
                b'Branch.get_tags_bytes', self._remote_path())
 
2365
            response = self._call('Branch.get_tags_bytes', self._remote_path())
3708
2366
        except errors.UnknownSmartMethod:
3709
2367
            medium._remember_remote_is_before((1, 13))
3710
2368
            return self._vfs_get_tags_bytes()
3715
2373
        return self._real_branch._set_tags_bytes(bytes)
3716
2374
 
3717
2375
    def _set_tags_bytes(self, bytes):
3718
 
        if self.is_locked():
3719
 
            self._tags_bytes = bytes
3720
2376
        medium = self._client._medium
3721
2377
        if medium._is_remote_before((1, 18)):
3722
2378
            self._vfs_set_tags_bytes(bytes)
3725
2381
            args = (
3726
2382
                self._remote_path(), self._lock_token, self._repo_lock_token)
3727
2383
            response = self._call_with_body_bytes(
3728
 
                b'Branch.set_tags_bytes', args, bytes)
 
2384
                'Branch.set_tags_bytes', args, bytes)
3729
2385
        except errors.UnknownSmartMethod:
3730
2386
            medium._remember_remote_is_before((1, 18))
3731
2387
            self._vfs_set_tags_bytes(bytes)
3732
2388
 
3733
2389
    def lock_read(self):
3734
 
        """Lock the branch for read operations.
3735
 
 
3736
 
        :return: A breezy.lock.LogicalLockResult.
3737
 
        """
3738
2390
        self.repository.lock_read()
3739
2391
        if not self._lock_mode:
3740
2392
            self._note_lock('r')
3744
2396
                self._real_branch.lock_read()
3745
2397
        else:
3746
2398
            self._lock_count += 1
3747
 
        return lock.LogicalLockResult(self.unlock)
3748
2399
 
3749
2400
    def _remote_lock_write(self, token):
3750
2401
        if token is None:
3751
 
            branch_token = repo_token = b''
 
2402
            branch_token = repo_token = ''
3752
2403
        else:
3753
2404
            branch_token = token
3754
 
            repo_token = self.repository.lock_write().repository_token
 
2405
            repo_token = self.repository.lock_write()
3755
2406
            self.repository.unlock()
3756
2407
        err_context = {'token': token}
3757
 
        try:
3758
 
            response = self._call(
3759
 
                b'Branch.lock_write', self._remote_path(), branch_token,
3760
 
                repo_token or b'', **err_context)
3761
 
        except errors.LockContention as e:
3762
 
            # The LockContention from the server doesn't have any
3763
 
            # information about the lock_url. We re-raise LockContention
3764
 
            # with valid lock_url.
3765
 
            raise errors.LockContention('(remote lock)',
3766
 
                                        self.repository.base.split('.bzr/')[0])
3767
 
        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':
3768
2412
            raise errors.UnexpectedSmartServerResponse(response)
3769
2413
        ok, branch_token, repo_token = response
3770
2414
        return branch_token, repo_token
3776
2420
            remote_tokens = self._remote_lock_write(token)
3777
2421
            self._lock_token, self._repo_lock_token = remote_tokens
3778
2422
            if not self._lock_token:
3779
 
                raise SmartProtocolError(
3780
 
                    'Remote server did not return a token!')
 
2423
                raise SmartProtocolError('Remote server did not return a token!')
3781
2424
            # Tell the self.repository object that it is locked.
3782
2425
            self.repository.lock_write(
3783
2426
                self._repo_lock_token, _skip_rpc=True)
3791
2434
            self._lock_mode = 'w'
3792
2435
            self._lock_count = 1
3793
2436
        elif self._lock_mode == 'r':
3794
 
            raise errors.ReadOnlyError(self)
 
2437
            raise errors.ReadOnlyTransaction
3795
2438
        else:
3796
2439
            if token is not None:
3797
2440
                # A token was given to lock_write, and we're relocking, so
3802
2445
            self._lock_count += 1
3803
2446
            # Re-lock the repository too.
3804
2447
            self.repository.lock_write(self._repo_lock_token)
3805
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
2448
        return self._lock_token or None
3806
2449
 
3807
2450
    def _unlock(self, branch_token, repo_token):
3808
2451
        err_context = {'token': str((branch_token, repo_token))}
3809
2452
        response = self._call(
3810
 
            b'Branch.unlock', self._remote_path(), branch_token,
3811
 
            repo_token or b'', **err_context)
3812
 
        if response == (b'ok',):
 
2453
            'Branch.unlock', self._remote_path(), branch_token,
 
2454
            repo_token or '', **err_context)
 
2455
        if response == ('ok',):
3813
2456
            return
3814
2457
        raise errors.UnexpectedSmartServerResponse(response)
3815
2458
 
3818
2461
        try:
3819
2462
            self._lock_count -= 1
3820
2463
            if not self._lock_count:
3821
 
                if self.conf_store is not None:
3822
 
                    self.conf_store.save_changes()
3823
2464
                self._clear_cached_state()
3824
2465
                mode = self._lock_mode
3825
2466
                self._lock_mode = None
3826
2467
                if self._real_branch is not None:
3827
 
                    if (not self._leave_lock and mode == 'w'
3828
 
                            and self._repo_lock_token):
 
2468
                    if (not self._leave_lock and mode == 'w' and
 
2469
                        self._repo_lock_token):
3829
2470
                        # If this RemoteBranch will remove the physical lock
3830
2471
                        # for the repository, make sure the _real_branch
3831
2472
                        # doesn't do it first.  (Because the _real_branch's
3848
2489
            self.repository.unlock()
3849
2490
 
3850
2491
    def break_lock(self):
3851
 
        try:
3852
 
            response = self._call(
3853
 
                b'Branch.break_lock', self._remote_path())
3854
 
        except errors.UnknownSmartMethod:
3855
 
            self._ensure_real()
3856
 
            return self._real_branch.break_lock()
3857
 
        if response != (b'ok',):
3858
 
            raise errors.UnexpectedSmartServerResponse(response)
 
2492
        self._ensure_real()
 
2493
        return self._real_branch.break_lock()
3859
2494
 
3860
2495
    def leave_lock_in_place(self):
3861
2496
        if not self._lock_token:
3867
2502
            raise NotImplementedError(self.dont_leave_lock_in_place)
3868
2503
        self._leave_lock = False
3869
2504
 
 
2505
    @needs_read_lock
3870
2506
    def get_rev_id(self, revno, history=None):
3871
2507
        if revno == 0:
3872
2508
            return _mod_revision.NULL_REVISION
3873
 
        with self.lock_read():
3874
 
            last_revision_info = self.last_revision_info()
3875
 
            ok, result = self.repository.get_rev_id_for_revno(
3876
 
                revno, last_revision_info)
3877
 
            if ok:
3878
 
                return result
3879
 
            missing_parent = result[1]
3880
 
            # Either the revision named by the server is missing, or its parent
3881
 
            # is.  Call get_parent_map to determine which, so that we report a
3882
 
            # useful error.
3883
 
            parent_map = self.repository.get_parent_map([missing_parent])
3884
 
            if missing_parent in parent_map:
3885
 
                missing_parent = parent_map[missing_parent]
3886
 
            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)
3887
2522
 
3888
 
    def _read_last_revision_info(self):
3889
 
        response = self._call(
3890
 
            b'Branch.last_revision_info', self._remote_path())
3891
 
        if response[0] != b'ok':
3892
 
            raise SmartProtocolError(
3893
 
                '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,))
3894
2527
        revno = int(response[1])
3895
2528
        last_revision = response[2]
3896
2529
        return (revno, last_revision)
3901
2534
            self._ensure_real()
3902
2535
            return self._real_branch._gen_revision_history()
3903
2536
        response_tuple, response_handler = self._call_expecting_body(
3904
 
            b'Branch.revision_history', self._remote_path())
3905
 
        if response_tuple[0] != b'ok':
 
2537
            'Branch.revision_history', self._remote_path())
 
2538
        if response_tuple[0] != 'ok':
3906
2539
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3907
 
        result = response_handler.read_body_bytes().split(b'\x00')
 
2540
        result = response_handler.read_body_bytes().split('\x00')
3908
2541
        if result == ['']:
3909
2542
            return []
3910
2543
        return result
3911
2544
 
3912
2545
    def _remote_path(self):
3913
 
        return self.controldir._path_for_remote_call(self._client)
 
2546
        return self.bzrdir._path_for_remote_call(self._client)
3914
2547
 
3915
2548
    def _set_last_revision_descendant(self, revision_id, other_branch,
3916
 
                                      allow_diverged=False, allow_overwrite_descendant=False):
 
2549
            allow_diverged=False, allow_overwrite_descendant=False):
3917
2550
        # This performs additional work to meet the hook contract; while its
3918
2551
        # undesirable, we have to synthesise the revno to call the hook, and
3919
2552
        # not calling the hook is worse as it means changes can't be prevented.
3924
2557
        history = self._lefthand_history(revision_id)
3925
2558
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3926
2559
        err_context = {'other_branch': other_branch}
3927
 
        response = self._call(b'Branch.set_last_revision_ex',
3928
 
                              self._remote_path(), self._lock_token, self._repo_lock_token,
3929
 
                              revision_id, int(allow_diverged), int(
3930
 
                                  allow_overwrite_descendant),
3931
 
                              **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)
3932
2564
        self._clear_cached_state()
3933
 
        if len(response) != 3 and response[0] != b'ok':
 
2565
        if len(response) != 3 and response[0] != 'ok':
3934
2566
            raise errors.UnexpectedSmartServerResponse(response)
3935
2567
        new_revno, new_revision_id = response[1:]
3936
2568
        self._last_revision_info_cache = new_revno, new_revision_id
3950
2582
        history = self._lefthand_history(revision_id)
3951
2583
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3952
2584
        self._clear_cached_state()
3953
 
        response = self._call(b'Branch.set_last_revision',
3954
 
                              self._remote_path(), self._lock_token, self._repo_lock_token,
3955
 
                              revision_id)
3956
 
        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',):
3957
2589
            raise errors.UnexpectedSmartServerResponse(response)
3958
2590
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3959
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
 
3960
2606
    def _get_parent_location(self):
3961
2607
        medium = self._client._medium
3962
2608
        if medium._is_remote_before((1, 13)):
3963
2609
            return self._vfs_get_parent_location()
3964
2610
        try:
3965
 
            response = self._call(b'Branch.get_parent', self._remote_path())
 
2611
            response = self._call('Branch.get_parent', self._remote_path())
3966
2612
        except errors.UnknownSmartMethod:
3967
2613
            medium._remember_remote_is_before((1, 13))
3968
2614
            return self._vfs_get_parent_location()
3969
2615
        if len(response) != 1:
3970
2616
            raise errors.UnexpectedSmartServerResponse(response)
3971
2617
        parent_location = response[0]
3972
 
        if parent_location == b'':
 
2618
        if parent_location == '':
3973
2619
            return None
3974
 
        return parent_location.decode('utf-8')
 
2620
        return parent_location
3975
2621
 
3976
2622
    def _vfs_get_parent_location(self):
3977
2623
        self._ensure_real()
3982
2628
        if medium._is_remote_before((1, 15)):
3983
2629
            return self._vfs_set_parent_location(url)
3984
2630
        try:
3985
 
            call_url = url or u''
3986
 
            if isinstance(call_url, text_type):
3987
 
                call_url = call_url.encode('utf-8')
3988
 
            response = self._call(b'Branch.set_parent_location',
3989
 
                                  self._remote_path(), self._lock_token, self._repo_lock_token,
3990
 
                                  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)
3991
2637
        except errors.UnknownSmartMethod:
3992
2638
            medium._remember_remote_is_before((1, 15))
3993
2639
            return self._vfs_set_parent_location(url)
3998
2644
        self._ensure_real()
3999
2645
        return self._real_branch._set_parent_location(url)
4000
2646
 
 
2647
    @needs_write_lock
4001
2648
    def pull(self, source, overwrite=False, stop_revision=None,
4002
2649
             **kwargs):
4003
 
        with self.lock_write():
4004
 
            self._clear_cached_state_of_remote_branch_only()
4005
 
            self._ensure_real()
4006
 
            return self._real_branch.pull(
4007
 
                source, overwrite=overwrite, stop_revision=stop_revision,
4008
 
                _override_hook_target=self, **kwargs)
4009
 
 
4010
 
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
4011
 
        with self.lock_read():
4012
 
            self._ensure_real()
4013
 
            return self._real_branch.push(
4014
 
                target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4015
 
                _override_hook_source_branch=self)
4016
 
 
4017
 
    def peek_lock_mode(self):
4018
 
        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)
4019
2662
 
4020
2663
    def is_locked(self):
4021
2664
        return self._lock_count >= 1
4022
2665
 
4023
 
    def revision_id_to_dotted_revno(self, revision_id):
4024
 
        """Given a revision id, return its dotted revno.
4025
 
 
4026
 
        :return: a tuple like (1,) or (400,1,3).
4027
 
        """
4028
 
        with self.lock_read():
4029
 
            try:
4030
 
                response = self._call(b'Branch.revision_id_to_revno',
4031
 
                                      self._remote_path(), revision_id)
4032
 
            except errors.UnknownSmartMethod:
4033
 
                self._ensure_real()
4034
 
                return self._real_branch.revision_id_to_dotted_revno(revision_id)
4035
 
            if response[0] == b'ok':
4036
 
                return tuple([int(x) for x in response[1:]])
4037
 
            else:
4038
 
                raise errors.UnexpectedSmartServerResponse(response)
4039
 
 
 
2666
    @needs_read_lock
4040
2667
    def revision_id_to_revno(self, revision_id):
4041
 
        """Given a revision id on the branch mainline, return its revno.
4042
 
 
4043
 
        :return: an integer
4044
 
        """
4045
 
        with self.lock_read():
4046
 
            try:
4047
 
                response = self._call(b'Branch.revision_id_to_revno',
4048
 
                                      self._remote_path(), revision_id)
4049
 
            except errors.UnknownSmartMethod:
4050
 
                self._ensure_real()
4051
 
                return self._real_branch.revision_id_to_revno(revision_id)
4052
 
            if response[0] == b'ok':
4053
 
                if len(response) == 2:
4054
 
                    return int(response[1])
4055
 
                raise NoSuchRevision(self, revision_id)
4056
 
            else:
4057
 
                raise errors.UnexpectedSmartServerResponse(response)
4058
 
 
 
2668
        self._ensure_real()
 
2669
        return self._real_branch.revision_id_to_revno(revision_id)
 
2670
 
 
2671
    @needs_write_lock
4059
2672
    def set_last_revision_info(self, revno, revision_id):
4060
 
        with self.lock_write():
4061
 
            # XXX: These should be returned by the set_last_revision_info verb
4062
 
            old_revno, old_revid = self.last_revision_info()
4063
 
            self._run_pre_change_branch_tip_hooks(revno, revision_id)
4064
 
            if not revision_id or not isinstance(revision_id, bytes):
4065
 
                raise errors.InvalidRevisionId(
4066
 
                    revision_id=revision_id, branch=self)
4067
 
            try:
4068
 
                response = self._call(b'Branch.set_last_revision_info',
4069
 
                                      self._remote_path(), self._lock_token, self._repo_lock_token,
4070
 
                                      str(revno).encode('ascii'), revision_id)
4071
 
            except errors.UnknownSmartMethod:
4072
 
                self._ensure_real()
4073
 
                self._clear_cached_state_of_remote_branch_only()
4074
 
                self._real_branch.set_last_revision_info(revno, revision_id)
4075
 
                self._last_revision_info_cache = revno, revision_id
4076
 
                return
4077
 
            if response == (b'ok',):
4078
 
                self._clear_cached_state()
4079
 
                self._last_revision_info_cache = revno, revision_id
4080
 
                self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4081
 
                # Update the _real_branch's cache too.
4082
 
                if self._real_branch is not None:
4083
 
                    cache = self._last_revision_info_cache
4084
 
                    self._real_branch._last_revision_info_cache = cache
4085
 
            else:
4086
 
                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)
4087
2697
 
 
2698
    @needs_write_lock
4088
2699
    def generate_revision_history(self, revision_id, last_rev=None,
4089
2700
                                  other_branch=None):
4090
 
        with self.lock_write():
4091
 
            medium = self._client._medium
4092
 
            if not medium._is_remote_before((1, 6)):
4093
 
                # Use a smart method for 1.6 and above servers
4094
 
                try:
4095
 
                    self._set_last_revision_descendant(revision_id, other_branch,
4096
 
                                                       allow_diverged=True, allow_overwrite_descendant=True)
4097
 
                    return
4098
 
                except errors.UnknownSmartMethod:
4099
 
                    medium._remember_remote_is_before((1, 6))
4100
 
            self._clear_cached_state_of_remote_branch_only()
4101
 
            graph = self.repository.get_graph()
4102
 
            (last_revno, last_revid) = self.last_revision_info()
4103
 
            known_revision_ids = [
4104
 
                (last_revid, last_revno),
4105
 
                (_mod_revision.NULL_REVISION, 0),
4106
 
                ]
4107
 
            if last_rev is not None:
4108
 
                if not graph.is_ancestor(last_rev, revision_id):
4109
 
                    # our previous tip is not merged into stop_revision
4110
 
                    raise errors.DivergedBranches(self, other_branch)
4111
 
            revno = graph.find_distance_to_null(
4112
 
                revision_id, known_revision_ids)
4113
 
            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))
4114
2713
 
4115
2714
    def set_push_location(self, location):
4116
 
        self._set_config_location('push_location', location)
4117
 
 
4118
 
    def heads_to_fetch(self):
4119
 
        if self._format._use_default_local_heads_to_fetch():
4120
 
            # We recognise this format, and its heads-to-fetch implementation
4121
 
            # is the default one (tip + tags).  In this case it's cheaper to
4122
 
            # just use the default implementation rather than a special RPC as
4123
 
            # the tip and tags data is cached.
4124
 
            return branch.Branch.heads_to_fetch(self)
4125
 
        medium = self._client._medium
4126
 
        if medium._is_remote_before((2, 4)):
4127
 
            return self._vfs_heads_to_fetch()
4128
 
        try:
4129
 
            return self._rpc_heads_to_fetch()
4130
 
        except errors.UnknownSmartMethod:
4131
 
            medium._remember_remote_is_before((2, 4))
4132
 
            return self._vfs_heads_to_fetch()
4133
 
 
4134
 
    def _rpc_heads_to_fetch(self):
4135
 
        response = self._call(b'Branch.heads_to_fetch', self._remote_path())
4136
 
        if len(response) != 2:
4137
 
            raise errors.UnexpectedSmartServerResponse(response)
4138
 
        must_fetch, if_present_fetch = response
4139
 
        return set(must_fetch), set(if_present_fetch)
4140
 
 
4141
 
    def _vfs_heads_to_fetch(self):
4142
2715
        self._ensure_real()
4143
 
        return self._real_branch.heads_to_fetch()
4144
 
 
4145
 
    def reconcile(self, thorough=True):
4146
 
        """Make sure the data stored in this branch is consistent."""
4147
 
        from .reconcile import BranchReconciler
4148
 
        with self.lock_write():
4149
 
            reconciler = BranchReconciler(self, thorough=thorough)
4150
 
            return reconciler.reconcile()
 
2716
        return self._real_branch.set_push_location(location)
4151
2717
 
4152
2718
 
4153
2719
class RemoteConfig(object):
4155
2721
 
4156
2722
    It is a low-level object that considers config data to be name/value pairs
4157
2723
    that may be associated with a section. Assigning meaning to the these
4158
 
    values is done at higher levels like breezy.config.TreeConfig.
 
2724
    values is done at higher levels like bzrlib.config.TreeConfig.
4159
2725
    """
4160
2726
 
4161
2727
    def get_option(self, name, section=None, default=None):
4168
2734
        """
4169
2735
        try:
4170
2736
            configobj = self._get_configobj()
4171
 
            section_obj = None
4172
2737
            if section is None:
4173
2738
                section_obj = configobj
4174
2739
            else:
4175
2740
                try:
4176
2741
                    section_obj = configobj[section]
4177
2742
                except KeyError:
4178
 
                    pass
4179
 
            if section_obj is None:
4180
 
                value = default
4181
 
            else:
4182
 
                value = section_obj.get(name, default)
 
2743
                    return default
 
2744
            return section_obj.get(name, default)
4183
2745
        except errors.UnknownSmartMethod:
4184
 
            value = self._vfs_get_option(name, section, default)
4185
 
        for hook in _mod_config.OldConfigHooks['get']:
4186
 
            hook(self, name, value)
4187
 
        return value
 
2746
            return self._vfs_get_option(name, section, default)
4188
2747
 
4189
2748
    def _response_to_configobj(self, response):
4190
 
        if len(response[0]) and response[0][0] != b'ok':
 
2749
        if len(response[0]) and response[0][0] != 'ok':
4191
2750
            raise errors.UnexpectedSmartServerResponse(response)
4192
2751
        lines = response[1].read_body_bytes().splitlines()
4193
 
        conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4194
 
        for hook in _mod_config.OldConfigHooks['load']:
4195
 
            hook(self)
4196
 
        return conf
 
2752
        return config.ConfigObj(lines, encoding='utf-8')
4197
2753
 
4198
2754
 
4199
2755
class RemoteBranchConfig(RemoteConfig):
4205
2761
    def _get_configobj(self):
4206
2762
        path = self._branch._remote_path()
4207
2763
        response = self._branch._client.call_expecting_body(
4208
 
            b'Branch.get_config_file', path)
 
2764
            'Branch.get_config_file', path)
4209
2765
        return self._response_to_configobj(response)
4210
2766
 
4211
2767
    def set_option(self, value, name, section=None):
4218
2774
        medium = self._branch._client._medium
4219
2775
        if medium._is_remote_before((1, 14)):
4220
2776
            return self._vfs_set_option(value, name, section)
4221
 
        if isinstance(value, dict):
4222
 
            if medium._is_remote_before((2, 2)):
4223
 
                return self._vfs_set_option(value, name, section)
4224
 
            return self._set_config_option_dict(value, name, section)
4225
 
        else:
4226
 
            return self._set_config_option(value, name, section)
4227
 
 
4228
 
    def _set_config_option(self, value, name, section):
4229
2777
        try:
4230
2778
            path = self._branch._remote_path()
4231
 
            response = self._branch._client.call(b'Branch.set_config_option',
4232
 
                                                 path, self._branch._lock_token, self._branch._repo_lock_token,
4233
 
                                                 value.encode(
4234
 
                                                     'utf8'), name.encode('utf-8'),
4235
 
                                                 (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 '')
4236
2782
        except errors.UnknownSmartMethod:
4237
 
            medium = self._branch._client._medium
4238
2783
            medium._remember_remote_is_before((1, 14))
4239
2784
            return self._vfs_set_option(value, name, section)
4240
2785
        if response != ():
4241
2786
            raise errors.UnexpectedSmartServerResponse(response)
4242
2787
 
4243
 
    def _serialize_option_dict(self, option_dict):
4244
 
        utf8_dict = {}
4245
 
        for key, value in option_dict.items():
4246
 
            if isinstance(key, text_type):
4247
 
                key = key.encode('utf8')
4248
 
            if isinstance(value, text_type):
4249
 
                value = value.encode('utf8')
4250
 
            utf8_dict[key] = value
4251
 
        return bencode.bencode(utf8_dict)
4252
 
 
4253
 
    def _set_config_option_dict(self, value, name, section):
4254
 
        try:
4255
 
            path = self._branch._remote_path()
4256
 
            serialised_dict = self._serialize_option_dict(value)
4257
 
            response = self._branch._client.call(
4258
 
                b'Branch.set_config_option_dict',
4259
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
4260
 
                serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
4261
 
        except errors.UnknownSmartMethod:
4262
 
            medium = self._branch._client._medium
4263
 
            medium._remember_remote_is_before((2, 2))
4264
 
            return self._vfs_set_option(value, name, section)
4265
 
        if response != ():
4266
 
            raise errors.UnexpectedSmartServerResponse(response)
4267
 
 
4268
2788
    def _real_object(self):
4269
2789
        self._branch._ensure_real()
4270
2790
        return self._branch._real_branch
4282
2802
 
4283
2803
    def _get_configobj(self):
4284
2804
        medium = self._bzrdir._client._medium
4285
 
        verb = b'BzrDir.get_config_file'
 
2805
        verb = 'BzrDir.get_config_file'
4286
2806
        if medium._is_remote_before((1, 15)):
4287
2807
            raise errors.UnknownSmartMethod(verb)
4288
2808
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4309
2829
        return self._bzrdir._real_bzrdir
4310
2830
 
4311
2831
 
4312
 
error_translators = registry.Registry()
4313
 
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)
4314
2840
 
4315
2841
 
4316
2842
def _translate_error(err, **context):
4330
2856
    def find(name):
4331
2857
        try:
4332
2858
            return context[name]
4333
 
        except KeyError:
4334
 
            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)
4335
2861
            raise err
4336
 
 
4337
2862
    def get_path():
4338
2863
        """Get the path from the context if present, otherwise use first error
4339
2864
        arg.
4340
2865
        """
4341
2866
        try:
4342
2867
            return context['path']
4343
 
        except KeyError:
 
2868
        except KeyError, key_err:
4344
2869
            try:
4345
 
                return err.error_args[0].decode('utf-8')
4346
 
            except IndexError:
4347
 
                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)
4348
2874
                raise err
4349
 
    if not isinstance(err.error_verb, bytes):
4350
 
        raise TypeError(err.error_verb)
4351
 
    try:
4352
 
        translator = error_translators.get(err.error_verb)
4353
 
    except KeyError:
4354
 
        pass
4355
 
    else:
4356
 
        raise translator(err, find, get_path)
4357
 
    try:
4358
 
        translator = no_context_error_translators.get(err.error_verb)
4359
 
    except KeyError:
4360
 
        raise errors.UnknownErrorFromSmartServer(err)
4361
 
    else:
4362
 
        raise translator(err)
4363
 
 
4364
 
 
4365
 
error_translators.register(b'NoSuchRevision',
4366
 
                           lambda err, find, get_path: NoSuchRevision(
4367
 
                               find('branch'), err.error_args[0]))
4368
 
error_translators.register(b'nosuchrevision',
4369
 
                           lambda err, find, get_path: NoSuchRevision(
4370
 
                               find('repository'), err.error_args[0]))
4371
 
 
4372
 
 
4373
 
def _translate_nobranch_error(err, find, get_path):
4374
 
    if len(err.error_args) >= 1:
4375
 
        extra = err.error_args[0].decode('utf-8')
4376
 
    else:
4377
 
        extra = None
4378
 
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4379
 
                                 detail=extra)
4380
 
 
4381
 
 
4382
 
error_translators.register(b'nobranch', _translate_nobranch_error)
4383
 
error_translators.register(b'norepository',
4384
 
                           lambda err, find, get_path: errors.NoRepositoryPresent(
4385
 
                               find('bzrdir')))
4386
 
error_translators.register(b'UnlockableTransport',
4387
 
                           lambda err, find, get_path: errors.UnlockableTransport(
4388
 
                               find('bzrdir').root_transport))
4389
 
error_translators.register(b'TokenMismatch',
4390
 
                           lambda err, find, get_path: errors.TokenMismatch(
4391
 
                               find('token'), '(remote token)'))
4392
 
error_translators.register(b'Diverged',
4393
 
                           lambda err, find, get_path: errors.DivergedBranches(
4394
 
                               find('branch'), find('other_branch')))
4395
 
error_translators.register(b'NotStacked',
4396
 
                           lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4397
 
 
4398
 
 
4399
 
def _translate_PermissionDenied(err, find, get_path):
4400
 
    path = get_path()
4401
 
    if len(err.error_args) >= 2:
4402
 
        extra = err.error_args[1].decode('utf-8')
4403
 
    else:
4404
 
        extra = None
4405
 
    return errors.PermissionDenied(path, extra=extra)
4406
 
 
4407
 
 
4408
 
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
4409
 
error_translators.register(b'ReadError',
4410
 
                           lambda err, find, get_path: errors.ReadError(get_path()))
4411
 
error_translators.register(b'NoSuchFile',
4412
 
                           lambda err, find, get_path: errors.NoSuchFile(get_path()))
4413
 
error_translators.register(b'TokenLockingNotSupported',
4414
 
                           lambda err, find, get_path: errors.TokenLockingNotSupported(
4415
 
                               find('repository')))
4416
 
error_translators.register(b'UnsuspendableWriteGroup',
4417
 
                           lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4418
 
                               repository=find('repository')))
4419
 
error_translators.register(b'UnresumableWriteGroup',
4420
 
                           lambda err, find, get_path: errors.UnresumableWriteGroup(
4421
 
                               repository=find('repository'), write_groups=err.error_args[0],
4422
 
                               reason=err.error_args[1]))
4423
 
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
4424
 
                                      lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
4425
 
no_context_error_translators.register(b'IncompatibleRepositories',
4426
 
                                      lambda err: errors.IncompatibleRepositories(
4427
 
                                          err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
4428
 
no_context_error_translators.register(b'LockContention',
4429
 
                                      lambda err: errors.LockContention('(remote lock)'))
4430
 
no_context_error_translators.register(b'LockFailed',
4431
 
                                      lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4432
 
no_context_error_translators.register(b'TipChangeRejected',
4433
 
                                      lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4434
 
no_context_error_translators.register(b'UnstackableBranchFormat',
4435
 
                                      lambda err: branch.UnstackableBranchFormat(*err.error_args))
4436
 
no_context_error_translators.register(b'UnstackableRepositoryFormat',
4437
 
                                      lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4438
 
no_context_error_translators.register(b'FileExists',
4439
 
                                      lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
4440
 
no_context_error_translators.register(b'DirectoryNotEmpty',
4441
 
                                      lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
4442
 
no_context_error_translators.register(b'UnknownFormat',
4443
 
                                      lambda err: errors.UnknownFormatError(
4444
 
                                          err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
4445
 
no_context_error_translators.register(b'InvalidURL',
4446
 
                                      lambda err: urlutils.InvalidURL(
4447
 
                                          err.error_args[0].decode('utf-8'), err.error_args[1].decode('ascii')))
4448
 
 
4449
 
 
4450
 
def _translate_short_readv_error(err):
4451
 
    args = err.error_args
4452
 
    return errors.ShortReadvError(
4453
 
        args[0].decode('utf-8'),
4454
 
        int(args[1].decode('ascii')), int(args[2].decode('ascii')),
4455
 
        int(args[3].decode('ascii')))
4456
 
 
4457
 
 
4458
 
no_context_error_translators.register(b'ShortReadvError',
4459
 
                                      _translate_short_readv_error)
4460
 
 
4461
 
 
4462
 
def _translate_unicode_error(err):
4463
 
    encoding = err.error_args[0].decode('ascii')
4464
 
    val = err.error_args[1].decode('utf-8')
4465
 
    start = int(err.error_args[2].decode('ascii'))
4466
 
    end = int(err.error_args[3].decode('ascii'))
4467
 
    reason = err.error_args[4].decode('utf-8')
4468
 
    if val.startswith('u:'):
4469
 
        val = val[2:].decode('utf-8')
4470
 
    elif val.startswith('s:'):
4471
 
        val = val[2:].decode('base64')
4472
 
    if err.error_verb == 'UnicodeDecodeError':
4473
 
        raise UnicodeDecodeError(encoding, val, start, end, reason)
4474
 
    elif err.error_verb == 'UnicodeEncodeError':
4475
 
        raise UnicodeEncodeError(encoding, val, start, end, reason)
4476
 
 
4477
 
 
4478
 
no_context_error_translators.register(b'UnicodeEncodeError',
4479
 
                                      _translate_unicode_error)
4480
 
no_context_error_translators.register(b'UnicodeDecodeError',
4481
 
                                      _translate_unicode_error)
4482
 
no_context_error_translators.register(b'ReadOnlyError',
4483
 
                                      lambda err: errors.TransportNotPossible('readonly transport'))
4484
 
no_context_error_translators.register(b'MemoryError',
4485
 
                                      lambda err: errors.BzrError("remote server out of memory\n"
4486
 
                                                                  "Retry non-remotely, or contact the server admin for details."))
4487
 
no_context_error_translators.register(b'RevisionNotPresent',
4488
 
                                      lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4489
 
 
4490
 
no_context_error_translators.register(b'BzrCheckError',
4491
 
                                      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)