/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

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

Show diffs side-by-side

added added

removed removed

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