/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 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

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