~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Packman
  • Date: 2011-12-08 19:00:14 UTC
  • mto: This revision was merged to the branch mainline in revision 6359.
  • Revision ID: martin.packman@canonical.com-20111208190014-mi8jm6v7jygmhb0r
Use --include-duplicates for make update-pot which already combines multiple msgid strings prettily

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import bz2
 
18
import zlib
 
19
 
 
20
from bzrlib import (
 
21
    bencode,
 
22
    branch,
 
23
    bzrdir as _mod_bzrdir,
 
24
    config,
 
25
    controldir,
 
26
    debug,
 
27
    errors,
 
28
    gpg,
 
29
    graph,
 
30
    lock,
 
31
    lockdir,
 
32
    osutils,
 
33
    registry,
 
34
    repository as _mod_repository,
 
35
    revision as _mod_revision,
 
36
    static_tuple,
 
37
    symbol_versioning,
 
38
    testament as _mod_testament,
 
39
    urlutils,
 
40
    vf_repository,
 
41
    vf_search,
 
42
    )
 
43
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
44
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
45
from bzrlib.errors import (
 
46
    NoSuchRevision,
 
47
    SmartProtocolError,
 
48
    )
 
49
from bzrlib.i18n import gettext
 
50
from bzrlib.inventory import Inventory
 
51
from bzrlib.lockable_files import LockableFiles
 
52
from bzrlib.smart import client, vfs, repository as smart_repo
 
53
from bzrlib.smart.client import _SmartClient
 
54
from bzrlib.revision import NULL_REVISION
 
55
from bzrlib.revisiontree import InventoryRevisionTree
 
56
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
 
57
from bzrlib.serializer import format_registry as serializer_format_registry
 
58
from bzrlib.trace import mutter, note, warning, log_exception_quietly
 
59
 
 
60
 
 
61
_DEFAULT_SEARCH_DEPTH = 100
 
62
 
 
63
 
 
64
class _RpcHelper(object):
 
65
    """Mixin class that helps with issuing RPCs."""
 
66
 
 
67
    def _call(self, method, *args, **err_context):
 
68
        try:
 
69
            return self._client.call(method, *args)
 
70
        except errors.ErrorFromSmartServer, err:
 
71
            self._translate_error(err, **err_context)
 
72
 
 
73
    def _call_expecting_body(self, method, *args, **err_context):
 
74
        try:
 
75
            return self._client.call_expecting_body(method, *args)
 
76
        except errors.ErrorFromSmartServer, err:
 
77
            self._translate_error(err, **err_context)
 
78
 
 
79
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
 
80
        try:
 
81
            return self._client.call_with_body_bytes(method, args, body_bytes)
 
82
        except errors.ErrorFromSmartServer, err:
 
83
            self._translate_error(err, **err_context)
 
84
 
 
85
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
 
86
                                             **err_context):
 
87
        try:
 
88
            return self._client.call_with_body_bytes_expecting_body(
 
89
                method, args, body_bytes)
 
90
        except errors.ErrorFromSmartServer, err:
 
91
            self._translate_error(err, **err_context)
 
92
 
 
93
 
 
94
def response_tuple_to_repo_format(response):
 
95
    """Convert a response tuple describing a repository format to a format."""
 
96
    format = RemoteRepositoryFormat()
 
97
    format._rich_root_data = (response[0] == 'yes')
 
98
    format._supports_tree_reference = (response[1] == 'yes')
 
99
    format._supports_external_lookups = (response[2] == 'yes')
 
100
    format._network_name = response[3]
 
101
    return format
 
102
 
 
103
 
 
104
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
 
105
# does not have to be imported unless a remote format is involved.
 
106
 
 
107
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
 
108
    """Format representing bzrdirs accessed via a smart server"""
 
109
 
 
110
    supports_workingtrees = False
 
111
 
 
112
    def __init__(self):
 
113
        _mod_bzrdir.BzrDirMetaFormat1.__init__(self)
 
114
        # XXX: It's a bit ugly that the network name is here, because we'd
 
115
        # like to believe that format objects are stateless or at least
 
116
        # immutable,  However, we do at least avoid mutating the name after
 
117
        # it's returned.  See <https://bugs.launchpad.net/bzr/+bug/504102>
 
118
        self._network_name = None
 
119
 
 
120
    def __repr__(self):
 
121
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
122
            self._network_name)
 
123
 
 
124
    def get_format_description(self):
 
125
        if self._network_name:
 
126
            try:
 
127
                real_format = controldir.network_format_registry.get(
 
128
                        self._network_name)
 
129
            except KeyError:
 
130
                pass
 
131
            else:
 
132
                return 'Remote: ' + real_format.get_format_description()
 
133
        return 'bzr remote bzrdir'
 
134
 
 
135
    def get_format_string(self):
 
136
        raise NotImplementedError(self.get_format_string)
 
137
 
 
138
    def network_name(self):
 
139
        if self._network_name:
 
140
            return self._network_name
 
141
        else:
 
142
            raise AssertionError("No network name set.")
 
143
 
 
144
    def initialize_on_transport(self, transport):
 
145
        try:
 
146
            # hand off the request to the smart server
 
147
            client_medium = transport.get_smart_medium()
 
148
        except errors.NoSmartMedium:
 
149
            # TODO: lookup the local format from a server hint.
 
150
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
151
            return local_dir_format.initialize_on_transport(transport)
 
152
        client = _SmartClient(client_medium)
 
153
        path = client.remote_path_from_transport(transport)
 
154
        try:
 
155
            response = client.call('BzrDirFormat.initialize', path)
 
156
        except errors.ErrorFromSmartServer, err:
 
157
            _translate_error(err, path=path)
 
158
        if response[0] != 'ok':
 
159
            raise errors.SmartProtocolError('unexpected response code %s' % (response,))
 
160
        format = RemoteBzrDirFormat()
 
161
        self._supply_sub_formats_to(format)
 
162
        return RemoteBzrDir(transport, format)
 
163
 
 
164
    def parse_NoneTrueFalse(self, arg):
 
165
        if not arg:
 
166
            return None
 
167
        if arg == 'False':
 
168
            return False
 
169
        if arg == 'True':
 
170
            return True
 
171
        raise AssertionError("invalid arg %r" % arg)
 
172
 
 
173
    def _serialize_NoneTrueFalse(self, arg):
 
174
        if arg is False:
 
175
            return 'False'
 
176
        if arg:
 
177
            return 'True'
 
178
        return ''
 
179
 
 
180
    def _serialize_NoneString(self, arg):
 
181
        return arg or ''
 
182
 
 
183
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
 
184
        create_prefix=False, force_new_repo=False, stacked_on=None,
 
185
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
 
186
        shared_repo=False):
 
187
        try:
 
188
            # hand off the request to the smart server
 
189
            client_medium = transport.get_smart_medium()
 
190
        except errors.NoSmartMedium:
 
191
            do_vfs = True
 
192
        else:
 
193
            # Decline to open it if the server doesn't support our required
 
194
            # version (3) so that the VFS-based transport will do it.
 
195
            if client_medium.should_probe():
 
196
                try:
 
197
                    server_version = client_medium.protocol_version()
 
198
                    if server_version != '2':
 
199
                        do_vfs = True
 
200
                    else:
 
201
                        do_vfs = False
 
202
                except errors.SmartProtocolError:
 
203
                    # Apparently there's no usable smart server there, even though
 
204
                    # the medium supports the smart protocol.
 
205
                    do_vfs = True
 
206
            else:
 
207
                do_vfs = False
 
208
        if not do_vfs:
 
209
            client = _SmartClient(client_medium)
 
210
            path = client.remote_path_from_transport(transport)
 
211
            if client_medium._is_remote_before((1, 16)):
 
212
                do_vfs = True
 
213
        if do_vfs:
 
214
            # TODO: lookup the local format from a server hint.
 
215
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
216
            self._supply_sub_formats_to(local_dir_format)
 
217
            return local_dir_format.initialize_on_transport_ex(transport,
 
218
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
219
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
220
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
221
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
222
                vfs_only=True)
 
223
        return self._initialize_on_transport_ex_rpc(client, path, transport,
 
224
            use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
225
            stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
 
226
 
 
227
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
 
228
        use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
229
        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
 
230
        args = []
 
231
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
 
232
        args.append(self._serialize_NoneTrueFalse(create_prefix))
 
233
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
 
234
        args.append(self._serialize_NoneString(stacked_on))
 
235
        # stack_on_pwd is often/usually our transport
 
236
        if stack_on_pwd:
 
237
            try:
 
238
                stack_on_pwd = transport.relpath(stack_on_pwd)
 
239
                if not stack_on_pwd:
 
240
                    stack_on_pwd = '.'
 
241
            except errors.PathNotChild:
 
242
                pass
 
243
        args.append(self._serialize_NoneString(stack_on_pwd))
 
244
        args.append(self._serialize_NoneString(repo_format_name))
 
245
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
 
246
        args.append(self._serialize_NoneTrueFalse(shared_repo))
 
247
        request_network_name = self._network_name or \
 
248
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
 
249
        try:
 
250
            response = client.call('BzrDirFormat.initialize_ex_1.16',
 
251
                request_network_name, path, *args)
 
252
        except errors.UnknownSmartMethod:
 
253
            client._medium._remember_remote_is_before((1,16))
 
254
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
255
            self._supply_sub_formats_to(local_dir_format)
 
256
            return local_dir_format.initialize_on_transport_ex(transport,
 
257
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
258
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
259
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
260
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
261
                vfs_only=True)
 
262
        except errors.ErrorFromSmartServer, err:
 
263
            _translate_error(err, path=path)
 
264
        repo_path = response[0]
 
265
        bzrdir_name = response[6]
 
266
        require_stacking = response[7]
 
267
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
 
268
        format = RemoteBzrDirFormat()
 
269
        format._network_name = bzrdir_name
 
270
        self._supply_sub_formats_to(format)
 
271
        bzrdir = RemoteBzrDir(transport, format, _client=client)
 
272
        if repo_path:
 
273
            repo_format = response_tuple_to_repo_format(response[1:])
 
274
            if repo_path == '.':
 
275
                repo_path = ''
 
276
            if repo_path:
 
277
                repo_bzrdir_format = RemoteBzrDirFormat()
 
278
                repo_bzrdir_format._network_name = response[5]
 
279
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
 
280
                    repo_bzrdir_format)
 
281
            else:
 
282
                repo_bzr = bzrdir
 
283
            final_stack = response[8] or None
 
284
            final_stack_pwd = response[9] or None
 
285
            if final_stack_pwd:
 
286
                final_stack_pwd = urlutils.join(
 
287
                    transport.base, final_stack_pwd)
 
288
            remote_repo = RemoteRepository(repo_bzr, repo_format)
 
289
            if len(response) > 10:
 
290
                # Updated server verb that locks remotely.
 
291
                repo_lock_token = response[10] or None
 
292
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
 
293
                if repo_lock_token:
 
294
                    remote_repo.dont_leave_lock_in_place()
 
295
            else:
 
296
                remote_repo.lock_write()
 
297
            policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
 
298
                final_stack_pwd, require_stacking)
 
299
            policy.acquire_repository()
 
300
        else:
 
301
            remote_repo = None
 
302
            policy = None
 
303
        bzrdir._format.set_branch_format(self.get_branch_format())
 
304
        if require_stacking:
 
305
            # The repo has already been created, but we need to make sure that
 
306
            # we'll make a stackable branch.
 
307
            bzrdir._format.require_stacking(_skip_repo=True)
 
308
        return remote_repo, bzrdir, require_stacking, policy
 
309
 
 
310
    def _open(self, transport):
 
311
        return RemoteBzrDir(transport, self)
 
312
 
 
313
    def __eq__(self, other):
 
314
        if not isinstance(other, RemoteBzrDirFormat):
 
315
            return False
 
316
        return self.get_format_description() == other.get_format_description()
 
317
 
 
318
    def __return_repository_format(self):
 
319
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
 
320
        # repository format has been asked for, tell the RemoteRepositoryFormat
 
321
        # that it should use that for init() etc.
 
322
        result = RemoteRepositoryFormat()
 
323
        custom_format = getattr(self, '_repository_format', None)
 
324
        if custom_format:
 
325
            if isinstance(custom_format, RemoteRepositoryFormat):
 
326
                return custom_format
 
327
            else:
 
328
                # We will use the custom format to create repositories over the
 
329
                # wire; expose its details like rich_root_data for code to
 
330
                # query
 
331
                result._custom_format = custom_format
 
332
        return result
 
333
 
 
334
    def get_branch_format(self):
 
335
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
 
336
        if not isinstance(result, RemoteBranchFormat):
 
337
            new_result = RemoteBranchFormat()
 
338
            new_result._custom_format = result
 
339
            # cache the result
 
340
            self.set_branch_format(new_result)
 
341
            result = new_result
 
342
        return result
 
343
 
 
344
    repository_format = property(__return_repository_format,
 
345
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
 
346
 
 
347
 
 
348
class RemoteControlStore(config.IniFileStore):
 
349
    """Control store which attempts to use HPSS calls to retrieve control store.
 
350
 
 
351
    Note that this is specific to bzr-based formats.
 
352
    """
 
353
 
 
354
    def __init__(self, bzrdir):
 
355
        super(RemoteControlStore, self).__init__()
 
356
        self.bzrdir = bzrdir
 
357
        self._real_store = None
 
358
 
 
359
    def lock_write(self, token=None):
 
360
        self._ensure_real()
 
361
        return self._real_store.lock_write(token)
 
362
 
 
363
    def unlock(self):
 
364
        self._ensure_real()
 
365
        return self._real_store.unlock()
 
366
 
 
367
    @needs_write_lock
 
368
    def save(self):
 
369
        # We need to be able to override the undecorated implementation
 
370
        self.save_without_locking()
 
371
 
 
372
    def save_without_locking(self):
 
373
        super(RemoteControlStore, self).save()
 
374
 
 
375
    def _ensure_real(self):
 
376
        self.bzrdir._ensure_real()
 
377
        if self._real_store is None:
 
378
            self._real_store = config.ControlStore(self.bzrdir)
 
379
 
 
380
    def external_url(self):
 
381
        return self.bzrdir.user_url
 
382
 
 
383
    def _load_content(self):
 
384
        medium = self.bzrdir._client._medium
 
385
        path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
 
386
        try:
 
387
            response, handler = self.bzrdir._call_expecting_body(
 
388
                'BzrDir.get_config_file', path)
 
389
        except errors.UnknownSmartMethod:
 
390
            self._ensure_real()
 
391
            return self._real_store._load_content()
 
392
        if len(response) and response[0] != 'ok':
 
393
            raise errors.UnexpectedSmartServerResponse(response)
 
394
        return handler.read_body_bytes()
 
395
 
 
396
    def _save_content(self, content):
 
397
        # FIXME JRV 2011-11-22: Ideally this should use a
 
398
        # HPSS call too, but at the moment it is not possible
 
399
        # to write lock control directories.
 
400
        self._ensure_real()
 
401
        return self._real_store._save_content(content)
 
402
 
 
403
 
 
404
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
 
405
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
406
 
 
407
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
408
        """Construct a RemoteBzrDir.
 
409
 
 
410
        :param _client: Private parameter for testing. Disables probing and the
 
411
            use of a real bzrdir.
 
412
        """
 
413
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
414
        # this object holds a delegated bzrdir that uses file-level operations
 
415
        # to talk to the other side
 
416
        self._real_bzrdir = None
 
417
        self._has_working_tree = None
 
418
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
 
419
        # create_branch for details.
 
420
        self._next_open_branch_result = None
 
421
 
 
422
        if _client is None:
 
423
            medium = transport.get_smart_medium()
 
424
            self._client = client._SmartClient(medium)
 
425
        else:
 
426
            self._client = _client
 
427
            if not _force_probe:
 
428
                return
 
429
 
 
430
        self._probe_bzrdir()
 
431
 
 
432
    def __repr__(self):
 
433
        return '%s(%r)' % (self.__class__.__name__, self._client)
 
434
 
 
435
    def _probe_bzrdir(self):
 
436
        medium = self._client._medium
 
437
        path = self._path_for_remote_call(self._client)
 
438
        if medium._is_remote_before((2, 1)):
 
439
            self._rpc_open(path)
 
440
            return
 
441
        try:
 
442
            self._rpc_open_2_1(path)
 
443
            return
 
444
        except errors.UnknownSmartMethod:
 
445
            medium._remember_remote_is_before((2, 1))
 
446
            self._rpc_open(path)
 
447
 
 
448
    def _rpc_open_2_1(self, path):
 
449
        response = self._call('BzrDir.open_2.1', path)
 
450
        if response == ('no',):
 
451
            raise errors.NotBranchError(path=self.root_transport.base)
 
452
        elif response[0] == 'yes':
 
453
            if response[1] == 'yes':
 
454
                self._has_working_tree = True
 
455
            elif response[1] == 'no':
 
456
                self._has_working_tree = False
 
457
            else:
 
458
                raise errors.UnexpectedSmartServerResponse(response)
 
459
        else:
 
460
            raise errors.UnexpectedSmartServerResponse(response)
 
461
 
 
462
    def _rpc_open(self, path):
 
463
        response = self._call('BzrDir.open', path)
 
464
        if response not in [('yes',), ('no',)]:
 
465
            raise errors.UnexpectedSmartServerResponse(response)
 
466
        if response == ('no',):
 
467
            raise errors.NotBranchError(path=self.root_transport.base)
 
468
 
 
469
    def _ensure_real(self):
 
470
        """Ensure that there is a _real_bzrdir set.
 
471
 
 
472
        Used before calls to self._real_bzrdir.
 
473
        """
 
474
        if not self._real_bzrdir:
 
475
            if 'hpssvfs' in debug.debug_flags:
 
476
                import traceback
 
477
                warning('VFS BzrDir access triggered\n%s',
 
478
                    ''.join(traceback.format_stack()))
 
479
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
 
480
                self.root_transport, _server_formats=False)
 
481
            self._format._network_name = \
 
482
                self._real_bzrdir._format.network_name()
 
483
 
 
484
    def _translate_error(self, err, **context):
 
485
        _translate_error(err, bzrdir=self, **context)
 
486
 
 
487
    def break_lock(self):
 
488
        # Prevent aliasing problems in the next_open_branch_result cache.
 
489
        # See create_branch for rationale.
 
490
        self._next_open_branch_result = None
 
491
        return _mod_bzrdir.BzrDir.break_lock(self)
 
492
 
 
493
    def _vfs_cloning_metadir(self, require_stacking=False):
 
494
        self._ensure_real()
 
495
        return self._real_bzrdir.cloning_metadir(
 
496
            require_stacking=require_stacking)
 
497
 
 
498
    def cloning_metadir(self, require_stacking=False):
 
499
        medium = self._client._medium
 
500
        if medium._is_remote_before((1, 13)):
 
501
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
502
        verb = 'BzrDir.cloning_metadir'
 
503
        if require_stacking:
 
504
            stacking = 'True'
 
505
        else:
 
506
            stacking = 'False'
 
507
        path = self._path_for_remote_call(self._client)
 
508
        try:
 
509
            response = self._call(verb, path, stacking)
 
510
        except errors.UnknownSmartMethod:
 
511
            medium._remember_remote_is_before((1, 13))
 
512
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
513
        except errors.UnknownErrorFromSmartServer, err:
 
514
            if err.error_tuple != ('BranchReference',):
 
515
                raise
 
516
            # We need to resolve the branch reference to determine the
 
517
            # cloning_metadir.  This causes unnecessary RPCs to open the
 
518
            # referenced branch (and bzrdir, etc) but only when the caller
 
519
            # didn't already resolve the branch reference.
 
520
            referenced_branch = self.open_branch()
 
521
            return referenced_branch.bzrdir.cloning_metadir()
 
522
        if len(response) != 3:
 
523
            raise errors.UnexpectedSmartServerResponse(response)
 
524
        control_name, repo_name, branch_info = response
 
525
        if len(branch_info) != 2:
 
526
            raise errors.UnexpectedSmartServerResponse(response)
 
527
        branch_ref, branch_name = branch_info
 
528
        try:
 
529
            format = controldir.network_format_registry.get(control_name)
 
530
        except KeyError:
 
531
            raise errors.UnknownFormatError(kind='control', format=control_name)
 
532
 
 
533
        if repo_name:
 
534
            try:
 
535
                format.repository_format = _mod_repository.network_format_registry.get(
 
536
                    repo_name)
 
537
            except KeyError:
 
538
                raise errors.UnknownFormatError(kind='repository',
 
539
                    format=repo_name)
 
540
        if branch_ref == 'ref':
 
541
            # XXX: we need possible_transports here to avoid reopening the
 
542
            # connection to the referenced location
 
543
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
 
544
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
 
545
            format.set_branch_format(branch_format)
 
546
        elif branch_ref == 'branch':
 
547
            if branch_name:
 
548
                try:
 
549
                    branch_format = branch.network_format_registry.get(
 
550
                        branch_name)
 
551
                except KeyError:
 
552
                    raise errors.UnknownFormatError(kind='branch',
 
553
                        format=branch_name)
 
554
                format.set_branch_format(branch_format)
 
555
        else:
 
556
            raise errors.UnexpectedSmartServerResponse(response)
 
557
        return format
 
558
 
 
559
    def create_repository(self, shared=False):
 
560
        # as per meta1 formats - just delegate to the format object which may
 
561
        # be parameterised.
 
562
        result = self._format.repository_format.initialize(self, shared)
 
563
        if not isinstance(result, RemoteRepository):
 
564
            return self.open_repository()
 
565
        else:
 
566
            return result
 
567
 
 
568
    def destroy_repository(self):
 
569
        """See BzrDir.destroy_repository"""
 
570
        path = self._path_for_remote_call(self._client)
 
571
        try:
 
572
            response = self._call('BzrDir.destroy_repository', path)
 
573
        except errors.UnknownSmartMethod:
 
574
            self._ensure_real()
 
575
            self._real_bzrdir.destroy_repository()
 
576
            return
 
577
        if response[0] != 'ok':
 
578
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
579
 
 
580
    def create_branch(self, name=None, repository=None,
 
581
                      append_revisions_only=None):
 
582
        # as per meta1 formats - just delegate to the format object which may
 
583
        # be parameterised.
 
584
        real_branch = self._format.get_branch_format().initialize(self,
 
585
            name=name, repository=repository,
 
586
            append_revisions_only=append_revisions_only)
 
587
        if not isinstance(real_branch, RemoteBranch):
 
588
            if not isinstance(repository, RemoteRepository):
 
589
                raise AssertionError(
 
590
                    'need a RemoteRepository to use with RemoteBranch, got %r'
 
591
                    % (repository,))
 
592
            result = RemoteBranch(self, repository, real_branch, name=name)
 
593
        else:
 
594
            result = real_branch
 
595
        # BzrDir.clone_on_transport() uses the result of create_branch but does
 
596
        # not return it to its callers; we save approximately 8% of our round
 
597
        # trips by handing the branch we created back to the first caller to
 
598
        # open_branch rather than probing anew. Long term we need a API in
 
599
        # bzrdir that doesn't discard result objects (like result_branch).
 
600
        # RBC 20090225
 
601
        self._next_open_branch_result = result
 
602
        return result
 
603
 
 
604
    def destroy_branch(self, name=None):
 
605
        """See BzrDir.destroy_branch"""
 
606
        path = self._path_for_remote_call(self._client)
 
607
        try:
 
608
            if name is not None:
 
609
                args = (name, )
 
610
            else:
 
611
                args = ()
 
612
            response = self._call('BzrDir.destroy_branch', path, *args)
 
613
        except errors.UnknownSmartMethod:
 
614
            self._ensure_real()
 
615
            self._real_bzrdir.destroy_branch(name=name)
 
616
            self._next_open_branch_result = None
 
617
            return
 
618
        self._next_open_branch_result = None
 
619
        if response[0] != 'ok':
 
620
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
621
 
 
622
    def create_workingtree(self, revision_id=None, from_branch=None,
 
623
        accelerator_tree=None, hardlink=False):
 
624
        raise errors.NotLocalUrl(self.transport.base)
 
625
 
 
626
    def find_branch_format(self, name=None):
 
627
        """Find the branch 'format' for this bzrdir.
 
628
 
 
629
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
630
        """
 
631
        b = self.open_branch(name=name)
 
632
        return b._format
 
633
 
 
634
    def get_branch_reference(self, name=None):
 
635
        """See BzrDir.get_branch_reference()."""
 
636
        if name is not None:
 
637
            # XXX JRV20100304: Support opening colocated branches
 
638
            raise errors.NoColocatedBranchSupport(self)
 
639
        response = self._get_branch_reference()
 
640
        if response[0] == 'ref':
 
641
            return response[1]
 
642
        else:
 
643
            return None
 
644
 
 
645
    def _get_branch_reference(self):
 
646
        path = self._path_for_remote_call(self._client)
 
647
        medium = self._client._medium
 
648
        candidate_calls = [
 
649
            ('BzrDir.open_branchV3', (2, 1)),
 
650
            ('BzrDir.open_branchV2', (1, 13)),
 
651
            ('BzrDir.open_branch', None),
 
652
            ]
 
653
        for verb, required_version in candidate_calls:
 
654
            if required_version and medium._is_remote_before(required_version):
 
655
                continue
 
656
            try:
 
657
                response = self._call(verb, path)
 
658
            except errors.UnknownSmartMethod:
 
659
                if required_version is None:
 
660
                    raise
 
661
                medium._remember_remote_is_before(required_version)
 
662
            else:
 
663
                break
 
664
        if verb == 'BzrDir.open_branch':
 
665
            if response[0] != 'ok':
 
666
                raise errors.UnexpectedSmartServerResponse(response)
 
667
            if response[1] != '':
 
668
                return ('ref', response[1])
 
669
            else:
 
670
                return ('branch', '')
 
671
        if response[0] not in ('ref', 'branch'):
 
672
            raise errors.UnexpectedSmartServerResponse(response)
 
673
        return response
 
674
 
 
675
    def _get_tree_branch(self, name=None):
 
676
        """See BzrDir._get_tree_branch()."""
 
677
        return None, self.open_branch(name=name)
 
678
 
 
679
    def open_branch(self, name=None, unsupported=False,
 
680
                    ignore_fallbacks=False, possible_transports=None):
 
681
        if unsupported:
 
682
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
683
        if self._next_open_branch_result is not None:
 
684
            # See create_branch for details.
 
685
            result = self._next_open_branch_result
 
686
            self._next_open_branch_result = None
 
687
            return result
 
688
        response = self._get_branch_reference()
 
689
        if response[0] == 'ref':
 
690
            # a branch reference, use the existing BranchReference logic.
 
691
            format = BranchReferenceFormat()
 
692
            return format.open(self, name=name, _found=True,
 
693
                location=response[1], ignore_fallbacks=ignore_fallbacks,
 
694
                possible_transports=possible_transports)
 
695
        branch_format_name = response[1]
 
696
        if not branch_format_name:
 
697
            branch_format_name = None
 
698
        format = RemoteBranchFormat(network_name=branch_format_name)
 
699
        return RemoteBranch(self, self.find_repository(), format=format,
 
700
            setup_stacking=not ignore_fallbacks, name=name,
 
701
            possible_transports=possible_transports)
 
702
 
 
703
    def _open_repo_v1(self, path):
 
704
        verb = 'BzrDir.find_repository'
 
705
        response = self._call(verb, path)
 
706
        if response[0] != 'ok':
 
707
            raise errors.UnexpectedSmartServerResponse(response)
 
708
        # servers that only support the v1 method don't support external
 
709
        # references either.
 
710
        self._ensure_real()
 
711
        repo = self._real_bzrdir.open_repository()
 
712
        response = response + ('no', repo._format.network_name())
 
713
        return response, repo
 
714
 
 
715
    def _open_repo_v2(self, path):
 
716
        verb = 'BzrDir.find_repositoryV2'
 
717
        response = self._call(verb, path)
 
718
        if response[0] != 'ok':
 
719
            raise errors.UnexpectedSmartServerResponse(response)
 
720
        self._ensure_real()
 
721
        repo = self._real_bzrdir.open_repository()
 
722
        response = response + (repo._format.network_name(),)
 
723
        return response, repo
 
724
 
 
725
    def _open_repo_v3(self, path):
 
726
        verb = 'BzrDir.find_repositoryV3'
 
727
        medium = self._client._medium
 
728
        if medium._is_remote_before((1, 13)):
 
729
            raise errors.UnknownSmartMethod(verb)
 
730
        try:
 
731
            response = self._call(verb, path)
 
732
        except errors.UnknownSmartMethod:
 
733
            medium._remember_remote_is_before((1, 13))
 
734
            raise
 
735
        if response[0] != 'ok':
 
736
            raise errors.UnexpectedSmartServerResponse(response)
 
737
        return response, None
 
738
 
 
739
    def open_repository(self):
 
740
        path = self._path_for_remote_call(self._client)
 
741
        response = None
 
742
        for probe in [self._open_repo_v3, self._open_repo_v2,
 
743
            self._open_repo_v1]:
 
744
            try:
 
745
                response, real_repo = probe(path)
 
746
                break
 
747
            except errors.UnknownSmartMethod:
 
748
                pass
 
749
        if response is None:
 
750
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
 
751
        if response[0] != 'ok':
 
752
            raise errors.UnexpectedSmartServerResponse(response)
 
753
        if len(response) != 6:
 
754
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
755
        if response[1] == '':
 
756
            # repo is at this dir.
 
757
            format = response_tuple_to_repo_format(response[2:])
 
758
            # Used to support creating a real format instance when needed.
 
759
            format._creating_bzrdir = self
 
760
            remote_repo = RemoteRepository(self, format)
 
761
            format._creating_repo = remote_repo
 
762
            if real_repo is not None:
 
763
                remote_repo._set_real_repository(real_repo)
 
764
            return remote_repo
 
765
        else:
 
766
            raise errors.NoRepositoryPresent(self)
 
767
 
 
768
    def has_workingtree(self):
 
769
        if self._has_working_tree is None:
 
770
            path = self._path_for_remote_call(self._client)
 
771
            try:
 
772
                response = self._call('BzrDir.has_workingtree', path)
 
773
            except errors.UnknownSmartMethod:
 
774
                self._ensure_real()
 
775
                self._has_working_tree = self._real_bzrdir.has_workingtree()
 
776
            else:
 
777
                if response[0] not in ('yes', 'no'):
 
778
                    raise SmartProtocolError('unexpected response code %s' % (response,))
 
779
                self._has_working_tree = (response[0] == 'yes')
 
780
        return self._has_working_tree
 
781
 
 
782
    def open_workingtree(self, recommend_upgrade=True):
 
783
        if self.has_workingtree():
 
784
            raise errors.NotLocalUrl(self.root_transport)
 
785
        else:
 
786
            raise errors.NoWorkingTree(self.root_transport.base)
 
787
 
 
788
    def _path_for_remote_call(self, client):
 
789
        """Return the path to be used for this bzrdir in a remote call."""
 
790
        return urlutils.split_segment_parameters_raw(
 
791
            client.remote_path_from_transport(self.root_transport))[0]
 
792
 
 
793
    def get_branch_transport(self, branch_format, name=None):
 
794
        self._ensure_real()
 
795
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
796
 
 
797
    def get_repository_transport(self, repository_format):
 
798
        self._ensure_real()
 
799
        return self._real_bzrdir.get_repository_transport(repository_format)
 
800
 
 
801
    def get_workingtree_transport(self, workingtree_format):
 
802
        self._ensure_real()
 
803
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
804
 
 
805
    def can_convert_format(self):
 
806
        """Upgrading of remote bzrdirs is not supported yet."""
 
807
        return False
 
808
 
 
809
    def needs_format_conversion(self, format):
 
810
        """Upgrading of remote bzrdirs is not supported yet."""
 
811
        return False
 
812
 
 
813
    def _get_config(self):
 
814
        return RemoteBzrDirConfig(self)
 
815
 
 
816
    def _get_config_store(self):
 
817
        return RemoteControlStore(self)
 
818
 
 
819
 
 
820
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
 
821
    """Format for repositories accessed over a _SmartClient.
 
822
 
 
823
    Instances of this repository are represented by RemoteRepository
 
824
    instances.
 
825
 
 
826
    The RemoteRepositoryFormat is parameterized during construction
 
827
    to reflect the capabilities of the real, remote format. Specifically
 
828
    the attributes rich_root_data and supports_tree_reference are set
 
829
    on a per instance basis, and are not set (and should not be) at
 
830
    the class level.
 
831
 
 
832
    :ivar _custom_format: If set, a specific concrete repository format that
 
833
        will be used when initializing a repository with this
 
834
        RemoteRepositoryFormat.
 
835
    :ivar _creating_repo: If set, the repository object that this
 
836
        RemoteRepositoryFormat was created for: it can be called into
 
837
        to obtain data like the network name.
 
838
    """
 
839
 
 
840
    _matchingbzrdir = RemoteBzrDirFormat()
 
841
    supports_full_versioned_files = True
 
842
    supports_leaving_lock = True
 
843
 
 
844
    def __init__(self):
 
845
        _mod_repository.RepositoryFormat.__init__(self)
 
846
        self._custom_format = None
 
847
        self._network_name = None
 
848
        self._creating_bzrdir = None
 
849
        self._revision_graph_can_have_wrong_parents = None
 
850
        self._supports_chks = None
 
851
        self._supports_external_lookups = None
 
852
        self._supports_tree_reference = None
 
853
        self._supports_funky_characters = None
 
854
        self._supports_nesting_repositories = None
 
855
        self._rich_root_data = None
 
856
 
 
857
    def __repr__(self):
 
858
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
859
            self._network_name)
 
860
 
 
861
    @property
 
862
    def fast_deltas(self):
 
863
        self._ensure_real()
 
864
        return self._custom_format.fast_deltas
 
865
 
 
866
    @property
 
867
    def rich_root_data(self):
 
868
        if self._rich_root_data is None:
 
869
            self._ensure_real()
 
870
            self._rich_root_data = self._custom_format.rich_root_data
 
871
        return self._rich_root_data
 
872
 
 
873
    @property
 
874
    def supports_chks(self):
 
875
        if self._supports_chks is None:
 
876
            self._ensure_real()
 
877
            self._supports_chks = self._custom_format.supports_chks
 
878
        return self._supports_chks
 
879
 
 
880
    @property
 
881
    def supports_external_lookups(self):
 
882
        if self._supports_external_lookups is None:
 
883
            self._ensure_real()
 
884
            self._supports_external_lookups = \
 
885
                self._custom_format.supports_external_lookups
 
886
        return self._supports_external_lookups
 
887
 
 
888
    @property
 
889
    def supports_funky_characters(self):
 
890
        if self._supports_funky_characters is None:
 
891
            self._ensure_real()
 
892
            self._supports_funky_characters = \
 
893
                self._custom_format.supports_funky_characters
 
894
        return self._supports_funky_characters
 
895
 
 
896
    @property
 
897
    def supports_nesting_repositories(self):
 
898
        if self._supports_nesting_repositories is None:
 
899
            self._ensure_real()
 
900
            self._supports_nesting_repositories = \
 
901
                self._custom_format.supports_nesting_repositories
 
902
        return self._supports_nesting_repositories
 
903
 
 
904
    @property
 
905
    def supports_tree_reference(self):
 
906
        if self._supports_tree_reference is None:
 
907
            self._ensure_real()
 
908
            self._supports_tree_reference = \
 
909
                self._custom_format.supports_tree_reference
 
910
        return self._supports_tree_reference
 
911
 
 
912
    @property
 
913
    def revision_graph_can_have_wrong_parents(self):
 
914
        if self._revision_graph_can_have_wrong_parents is None:
 
915
            self._ensure_real()
 
916
            self._revision_graph_can_have_wrong_parents = \
 
917
                self._custom_format.revision_graph_can_have_wrong_parents
 
918
        return self._revision_graph_can_have_wrong_parents
 
919
 
 
920
    def _vfs_initialize(self, a_bzrdir, shared):
 
921
        """Helper for common code in initialize."""
 
922
        if self._custom_format:
 
923
            # Custom format requested
 
924
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
925
        elif self._creating_bzrdir is not None:
 
926
            # Use the format that the repository we were created to back
 
927
            # has.
 
928
            prior_repo = self._creating_bzrdir.open_repository()
 
929
            prior_repo._ensure_real()
 
930
            result = prior_repo._real_repository._format.initialize(
 
931
                a_bzrdir, shared=shared)
 
932
        else:
 
933
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
 
934
            # support remote initialization.
 
935
            # We delegate to a real object at this point (as RemoteBzrDir
 
936
            # delegate to the repository format which would lead to infinite
 
937
            # recursion if we just called a_bzrdir.create_repository.
 
938
            a_bzrdir._ensure_real()
 
939
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
940
        if not isinstance(result, RemoteRepository):
 
941
            return self.open(a_bzrdir)
 
942
        else:
 
943
            return result
 
944
 
 
945
    def initialize(self, a_bzrdir, shared=False):
 
946
        # Being asked to create on a non RemoteBzrDir:
 
947
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
948
            return self._vfs_initialize(a_bzrdir, shared)
 
949
        medium = a_bzrdir._client._medium
 
950
        if medium._is_remote_before((1, 13)):
 
951
            return self._vfs_initialize(a_bzrdir, shared)
 
952
        # Creating on a remote bzr dir.
 
953
        # 1) get the network name to use.
 
954
        if self._custom_format:
 
955
            network_name = self._custom_format.network_name()
 
956
        elif self._network_name:
 
957
            network_name = self._network_name
 
958
        else:
 
959
            # Select the current bzrlib default and ask for that.
 
960
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
 
961
            reference_format = reference_bzrdir_format.repository_format
 
962
            network_name = reference_format.network_name()
 
963
        # 2) try direct creation via RPC
 
964
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
965
        verb = 'BzrDir.create_repository'
 
966
        if shared:
 
967
            shared_str = 'True'
 
968
        else:
 
969
            shared_str = 'False'
 
970
        try:
 
971
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
972
        except errors.UnknownSmartMethod:
 
973
            # Fallback - use vfs methods
 
974
            medium._remember_remote_is_before((1, 13))
 
975
            return self._vfs_initialize(a_bzrdir, shared)
 
976
        else:
 
977
            # Turn the response into a RemoteRepository object.
 
978
            format = response_tuple_to_repo_format(response[1:])
 
979
            # Used to support creating a real format instance when needed.
 
980
            format._creating_bzrdir = a_bzrdir
 
981
            remote_repo = RemoteRepository(a_bzrdir, format)
 
982
            format._creating_repo = remote_repo
 
983
            return remote_repo
 
984
 
 
985
    def open(self, a_bzrdir):
 
986
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
987
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
988
        return a_bzrdir.open_repository()
 
989
 
 
990
    def _ensure_real(self):
 
991
        if self._custom_format is None:
 
992
            try:
 
993
                self._custom_format = _mod_repository.network_format_registry.get(
 
994
                    self._network_name)
 
995
            except KeyError:
 
996
                raise errors.UnknownFormatError(kind='repository',
 
997
                    format=self._network_name)
 
998
 
 
999
    @property
 
1000
    def _fetch_order(self):
 
1001
        self._ensure_real()
 
1002
        return self._custom_format._fetch_order
 
1003
 
 
1004
    @property
 
1005
    def _fetch_uses_deltas(self):
 
1006
        self._ensure_real()
 
1007
        return self._custom_format._fetch_uses_deltas
 
1008
 
 
1009
    @property
 
1010
    def _fetch_reconcile(self):
 
1011
        self._ensure_real()
 
1012
        return self._custom_format._fetch_reconcile
 
1013
 
 
1014
    def get_format_description(self):
 
1015
        self._ensure_real()
 
1016
        return 'Remote: ' + self._custom_format.get_format_description()
 
1017
 
 
1018
    def __eq__(self, other):
 
1019
        return self.__class__ is other.__class__
 
1020
 
 
1021
    def network_name(self):
 
1022
        if self._network_name:
 
1023
            return self._network_name
 
1024
        self._creating_repo._ensure_real()
 
1025
        return self._creating_repo._real_repository._format.network_name()
 
1026
 
 
1027
    @property
 
1028
    def pack_compresses(self):
 
1029
        self._ensure_real()
 
1030
        return self._custom_format.pack_compresses
 
1031
 
 
1032
    @property
 
1033
    def _serializer(self):
 
1034
        self._ensure_real()
 
1035
        return self._custom_format._serializer
 
1036
 
 
1037
 
 
1038
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
 
1039
        lock._RelockDebugMixin):
 
1040
    """Repository accessed over rpc.
 
1041
 
 
1042
    For the moment most operations are performed using local transport-backed
 
1043
    Repository objects.
 
1044
    """
 
1045
 
 
1046
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
1047
        """Create a RemoteRepository instance.
 
1048
 
 
1049
        :param remote_bzrdir: The bzrdir hosting this repository.
 
1050
        :param format: The RemoteFormat object to use.
 
1051
        :param real_repository: If not None, a local implementation of the
 
1052
            repository logic for the repository, usually accessing the data
 
1053
            via the VFS.
 
1054
        :param _client: Private testing parameter - override the smart client
 
1055
            to be used by the repository.
 
1056
        """
 
1057
        if real_repository:
 
1058
            self._real_repository = real_repository
 
1059
        else:
 
1060
            self._real_repository = None
 
1061
        self.bzrdir = remote_bzrdir
 
1062
        if _client is None:
 
1063
            self._client = remote_bzrdir._client
 
1064
        else:
 
1065
            self._client = _client
 
1066
        self._format = format
 
1067
        self._lock_mode = None
 
1068
        self._lock_token = None
 
1069
        self._write_group_tokens = None
 
1070
        self._lock_count = 0
 
1071
        self._leave_lock = False
 
1072
        # Cache of revision parents; misses are cached during read locks, and
 
1073
        # write locks when no _real_repository has been set.
 
1074
        self._unstacked_provider = graph.CachingParentsProvider(
 
1075
            get_parent_map=self._get_parent_map_rpc)
 
1076
        self._unstacked_provider.disable_cache()
 
1077
        # For tests:
 
1078
        # These depend on the actual remote format, so force them off for
 
1079
        # maximum compatibility. XXX: In future these should depend on the
 
1080
        # remote repository instance, but this is irrelevant until we perform
 
1081
        # reconcile via an RPC call.
 
1082
        self._reconcile_does_inventory_gc = False
 
1083
        self._reconcile_fixes_text_parents = False
 
1084
        self._reconcile_backsup_inventory = False
 
1085
        self.base = self.bzrdir.transport.base
 
1086
        # Additional places to query for data.
 
1087
        self._fallback_repositories = []
 
1088
 
 
1089
    @property
 
1090
    def user_transport(self):
 
1091
        return self.bzrdir.user_transport
 
1092
 
 
1093
    @property
 
1094
    def control_transport(self):
 
1095
        # XXX: Normally you shouldn't directly get at the remote repository
 
1096
        # transport, but I'm not sure it's worth making this method
 
1097
        # optional -- mbp 2010-04-21
 
1098
        return self.bzrdir.get_repository_transport(None)
 
1099
 
 
1100
    def __str__(self):
 
1101
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
1102
 
 
1103
    __repr__ = __str__
 
1104
 
 
1105
    def abort_write_group(self, suppress_errors=False):
 
1106
        """Complete a write group on the decorated repository.
 
1107
 
 
1108
        Smart methods perform operations in a single step so this API
 
1109
        is not really applicable except as a compatibility thunk
 
1110
        for older plugins that don't use e.g. the CommitBuilder
 
1111
        facility.
 
1112
 
 
1113
        :param suppress_errors: see Repository.abort_write_group.
 
1114
        """
 
1115
        if self._real_repository:
 
1116
            self._ensure_real()
 
1117
            return self._real_repository.abort_write_group(
 
1118
                suppress_errors=suppress_errors)
 
1119
        if not self.is_in_write_group():
 
1120
            if suppress_errors:
 
1121
                mutter('(suppressed) not in write group')
 
1122
                return
 
1123
            raise errors.BzrError("not in write group")
 
1124
        path = self.bzrdir._path_for_remote_call(self._client)
 
1125
        try:
 
1126
            response = self._call('Repository.abort_write_group', path,
 
1127
                self._lock_token, self._write_group_tokens)
 
1128
        except Exception, exc:
 
1129
            self._write_group = None
 
1130
            if not suppress_errors:
 
1131
                raise
 
1132
            mutter('abort_write_group failed')
 
1133
            log_exception_quietly()
 
1134
            note(gettext('bzr: ERROR (ignored): %s'), exc)
 
1135
        else:
 
1136
            if response != ('ok', ):
 
1137
                raise errors.UnexpectedSmartServerResponse(response)
 
1138
            self._write_group_tokens = None
 
1139
 
 
1140
    @property
 
1141
    def chk_bytes(self):
 
1142
        """Decorate the real repository for now.
 
1143
 
 
1144
        In the long term a full blown network facility is needed to avoid
 
1145
        creating a real repository object locally.
 
1146
        """
 
1147
        self._ensure_real()
 
1148
        return self._real_repository.chk_bytes
 
1149
 
 
1150
    def commit_write_group(self):
 
1151
        """Complete a write group on the decorated repository.
 
1152
 
 
1153
        Smart methods perform operations in a single step so this API
 
1154
        is not really applicable except as a compatibility thunk
 
1155
        for older plugins that don't use e.g. the CommitBuilder
 
1156
        facility.
 
1157
        """
 
1158
        if self._real_repository:
 
1159
            self._ensure_real()
 
1160
            return self._real_repository.commit_write_group()
 
1161
        if not self.is_in_write_group():
 
1162
            raise errors.BzrError("not in write group")
 
1163
        path = self.bzrdir._path_for_remote_call(self._client)
 
1164
        response = self._call('Repository.commit_write_group', path,
 
1165
            self._lock_token, self._write_group_tokens)
 
1166
        if response != ('ok', ):
 
1167
            raise errors.UnexpectedSmartServerResponse(response)
 
1168
        self._write_group_tokens = None
 
1169
 
 
1170
    def resume_write_group(self, tokens):
 
1171
        if self._real_repository:
 
1172
            return self._real_repository.resume_write_group(tokens)
 
1173
        path = self.bzrdir._path_for_remote_call(self._client)
 
1174
        try:
 
1175
            response = self._call('Repository.check_write_group', path,
 
1176
               self._lock_token, tokens)
 
1177
        except errors.UnknownSmartMethod:
 
1178
            self._ensure_real()
 
1179
            return self._real_repository.resume_write_group(tokens)
 
1180
        if response != ('ok', ):
 
1181
            raise errors.UnexpectedSmartServerResponse(response)
 
1182
        self._write_group_tokens = tokens
 
1183
 
 
1184
    def suspend_write_group(self):
 
1185
        if self._real_repository:
 
1186
            return self._real_repository.suspend_write_group()
 
1187
        ret = self._write_group_tokens or []
 
1188
        self._write_group_tokens = None
 
1189
        return ret
 
1190
 
 
1191
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
1192
        self._ensure_real()
 
1193
        return self._real_repository.get_missing_parent_inventories(
 
1194
            check_for_missing_texts=check_for_missing_texts)
 
1195
 
 
1196
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
 
1197
        self._ensure_real()
 
1198
        return self._real_repository.get_rev_id_for_revno(
 
1199
            revno, known_pair)
 
1200
 
 
1201
    def get_rev_id_for_revno(self, revno, known_pair):
 
1202
        """See Repository.get_rev_id_for_revno."""
 
1203
        path = self.bzrdir._path_for_remote_call(self._client)
 
1204
        try:
 
1205
            if self._client._medium._is_remote_before((1, 17)):
 
1206
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
1207
            response = self._call(
 
1208
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
1209
        except errors.UnknownSmartMethod:
 
1210
            self._client._medium._remember_remote_is_before((1, 17))
 
1211
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
1212
        if response[0] == 'ok':
 
1213
            return True, response[1]
 
1214
        elif response[0] == 'history-incomplete':
 
1215
            known_pair = response[1:3]
 
1216
            for fallback in self._fallback_repositories:
 
1217
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
 
1218
                if found:
 
1219
                    return True, result
 
1220
                else:
 
1221
                    known_pair = result
 
1222
            # Not found in any fallbacks
 
1223
            return False, known_pair
 
1224
        else:
 
1225
            raise errors.UnexpectedSmartServerResponse(response)
 
1226
 
 
1227
    def _ensure_real(self):
 
1228
        """Ensure that there is a _real_repository set.
 
1229
 
 
1230
        Used before calls to self._real_repository.
 
1231
 
 
1232
        Note that _ensure_real causes many roundtrips to the server which are
 
1233
        not desirable, and prevents the use of smart one-roundtrip RPC's to
 
1234
        perform complex operations (such as accessing parent data, streaming
 
1235
        revisions etc). Adding calls to _ensure_real should only be done when
 
1236
        bringing up new functionality, adding fallbacks for smart methods that
 
1237
        require a fallback path, and never to replace an existing smart method
 
1238
        invocation. If in doubt chat to the bzr network team.
 
1239
        """
 
1240
        if self._real_repository is None:
 
1241
            if 'hpssvfs' in debug.debug_flags:
 
1242
                import traceback
 
1243
                warning('VFS Repository access triggered\n%s',
 
1244
                    ''.join(traceback.format_stack()))
 
1245
            self._unstacked_provider.missing_keys.clear()
 
1246
            self.bzrdir._ensure_real()
 
1247
            self._set_real_repository(
 
1248
                self.bzrdir._real_bzrdir.open_repository())
 
1249
 
 
1250
    def _translate_error(self, err, **context):
 
1251
        self.bzrdir._translate_error(err, repository=self, **context)
 
1252
 
 
1253
    def find_text_key_references(self):
 
1254
        """Find the text key references within the repository.
 
1255
 
 
1256
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
1257
            to whether they were referred to by the inventory of the
 
1258
            revision_id that they contain. The inventory texts from all present
 
1259
            revision ids are assessed to generate this report.
 
1260
        """
 
1261
        self._ensure_real()
 
1262
        return self._real_repository.find_text_key_references()
 
1263
 
 
1264
    def _generate_text_key_index(self):
 
1265
        """Generate a new text key index for the repository.
 
1266
 
 
1267
        This is an expensive function that will take considerable time to run.
 
1268
 
 
1269
        :return: A dict mapping (file_id, revision_id) tuples to a list of
 
1270
            parents, also (file_id, revision_id) tuples.
 
1271
        """
 
1272
        self._ensure_real()
 
1273
        return self._real_repository._generate_text_key_index()
 
1274
 
 
1275
    def _get_revision_graph(self, revision_id):
 
1276
        """Private method for using with old (< 1.2) servers to fallback."""
 
1277
        if revision_id is None:
 
1278
            revision_id = ''
 
1279
        elif _mod_revision.is_null(revision_id):
 
1280
            return {}
 
1281
 
 
1282
        path = self.bzrdir._path_for_remote_call(self._client)
 
1283
        response = self._call_expecting_body(
 
1284
            'Repository.get_revision_graph', path, revision_id)
 
1285
        response_tuple, response_handler = response
 
1286
        if response_tuple[0] != 'ok':
 
1287
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1288
        coded = response_handler.read_body_bytes()
 
1289
        if coded == '':
 
1290
            # no revisions in this repository!
 
1291
            return {}
 
1292
        lines = coded.split('\n')
 
1293
        revision_graph = {}
 
1294
        for line in lines:
 
1295
            d = tuple(line.split())
 
1296
            revision_graph[d[0]] = d[1:]
 
1297
 
 
1298
        return revision_graph
 
1299
 
 
1300
    def _get_sink(self):
 
1301
        """See Repository._get_sink()."""
 
1302
        return RemoteStreamSink(self)
 
1303
 
 
1304
    def _get_source(self, to_format):
 
1305
        """Return a source for streaming from this repository."""
 
1306
        return RemoteStreamSource(self, to_format)
 
1307
 
 
1308
    @needs_read_lock
 
1309
    def get_file_graph(self):
 
1310
        return graph.Graph(self.texts)
 
1311
 
 
1312
    @needs_read_lock
 
1313
    def has_revision(self, revision_id):
 
1314
        """True if this repository has a copy of the revision."""
 
1315
        # Copy of bzrlib.repository.Repository.has_revision
 
1316
        return revision_id in self.has_revisions((revision_id,))
 
1317
 
 
1318
    @needs_read_lock
 
1319
    def has_revisions(self, revision_ids):
 
1320
        """Probe to find out the presence of multiple revisions.
 
1321
 
 
1322
        :param revision_ids: An iterable of revision_ids.
 
1323
        :return: A set of the revision_ids that were present.
 
1324
        """
 
1325
        # Copy of bzrlib.repository.Repository.has_revisions
 
1326
        parent_map = self.get_parent_map(revision_ids)
 
1327
        result = set(parent_map)
 
1328
        if _mod_revision.NULL_REVISION in revision_ids:
 
1329
            result.add(_mod_revision.NULL_REVISION)
 
1330
        return result
 
1331
 
 
1332
    def _has_same_fallbacks(self, other_repo):
 
1333
        """Returns true if the repositories have the same fallbacks."""
 
1334
        # XXX: copied from Repository; it should be unified into a base class
 
1335
        # <https://bugs.launchpad.net/bzr/+bug/401622>
 
1336
        my_fb = self._fallback_repositories
 
1337
        other_fb = other_repo._fallback_repositories
 
1338
        if len(my_fb) != len(other_fb):
 
1339
            return False
 
1340
        for f, g in zip(my_fb, other_fb):
 
1341
            if not f.has_same_location(g):
 
1342
                return False
 
1343
        return True
 
1344
 
 
1345
    def has_same_location(self, other):
 
1346
        # TODO: Move to RepositoryBase and unify with the regular Repository
 
1347
        # one; unfortunately the tests rely on slightly different behaviour at
 
1348
        # present -- mbp 20090710
 
1349
        return (self.__class__ is other.__class__ and
 
1350
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
1351
 
 
1352
    def get_graph(self, other_repository=None):
 
1353
        """Return the graph for this repository format"""
 
1354
        parents_provider = self._make_parents_provider(other_repository)
 
1355
        return graph.Graph(parents_provider)
 
1356
 
 
1357
    @needs_read_lock
 
1358
    def get_known_graph_ancestry(self, revision_ids):
 
1359
        """Return the known graph for a set of revision ids and their ancestors.
 
1360
        """
 
1361
        st = static_tuple.StaticTuple
 
1362
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
1363
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
1364
        return graph.GraphThunkIdsToKeys(known_graph)
 
1365
 
 
1366
    def gather_stats(self, revid=None, committers=None):
 
1367
        """See Repository.gather_stats()."""
 
1368
        path = self.bzrdir._path_for_remote_call(self._client)
 
1369
        # revid can be None to indicate no revisions, not just NULL_REVISION
 
1370
        if revid is None or _mod_revision.is_null(revid):
 
1371
            fmt_revid = ''
 
1372
        else:
 
1373
            fmt_revid = revid
 
1374
        if committers is None or not committers:
 
1375
            fmt_committers = 'no'
 
1376
        else:
 
1377
            fmt_committers = 'yes'
 
1378
        response_tuple, response_handler = self._call_expecting_body(
 
1379
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
1380
        if response_tuple[0] != 'ok':
 
1381
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1382
 
 
1383
        body = response_handler.read_body_bytes()
 
1384
        result = {}
 
1385
        for line in body.split('\n'):
 
1386
            if not line:
 
1387
                continue
 
1388
            key, val_text = line.split(':')
 
1389
            if key in ('revisions', 'size', 'committers'):
 
1390
                result[key] = int(val_text)
 
1391
            elif key in ('firstrev', 'latestrev'):
 
1392
                values = val_text.split(' ')[1:]
 
1393
                result[key] = (float(values[0]), long(values[1]))
 
1394
 
 
1395
        return result
 
1396
 
 
1397
    def find_branches(self, using=False):
 
1398
        """See Repository.find_branches()."""
 
1399
        # should be an API call to the server.
 
1400
        self._ensure_real()
 
1401
        return self._real_repository.find_branches(using=using)
 
1402
 
 
1403
    def get_physical_lock_status(self):
 
1404
        """See Repository.get_physical_lock_status()."""
 
1405
        path = self.bzrdir._path_for_remote_call(self._client)
 
1406
        try:
 
1407
            response = self._call('Repository.get_physical_lock_status', path)
 
1408
        except errors.UnknownSmartMethod:
 
1409
            self._ensure_real()
 
1410
            return self._real_repository.get_physical_lock_status()
 
1411
        if response[0] not in ('yes', 'no'):
 
1412
            raise errors.UnexpectedSmartServerResponse(response)
 
1413
        return (response[0] == 'yes')
 
1414
 
 
1415
    def is_in_write_group(self):
 
1416
        """Return True if there is an open write group.
 
1417
 
 
1418
        write groups are only applicable locally for the smart server..
 
1419
        """
 
1420
        if self._write_group_tokens is not None:
 
1421
            return True
 
1422
        if self._real_repository:
 
1423
            return self._real_repository.is_in_write_group()
 
1424
 
 
1425
    def is_locked(self):
 
1426
        return self._lock_count >= 1
 
1427
 
 
1428
    def is_shared(self):
 
1429
        """See Repository.is_shared()."""
 
1430
        path = self.bzrdir._path_for_remote_call(self._client)
 
1431
        response = self._call('Repository.is_shared', path)
 
1432
        if response[0] not in ('yes', 'no'):
 
1433
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1434
        return response[0] == 'yes'
 
1435
 
 
1436
    def is_write_locked(self):
 
1437
        return self._lock_mode == 'w'
 
1438
 
 
1439
    def _warn_if_deprecated(self, branch=None):
 
1440
        # If we have a real repository, the check will be done there, if we
 
1441
        # don't the check will be done remotely.
 
1442
        pass
 
1443
 
 
1444
    def lock_read(self):
 
1445
        """Lock the repository for read operations.
 
1446
 
 
1447
        :return: A bzrlib.lock.LogicalLockResult.
 
1448
        """
 
1449
        # wrong eventually - want a local lock cache context
 
1450
        if not self._lock_mode:
 
1451
            self._note_lock('r')
 
1452
            self._lock_mode = 'r'
 
1453
            self._lock_count = 1
 
1454
            self._unstacked_provider.enable_cache(cache_misses=True)
 
1455
            if self._real_repository is not None:
 
1456
                self._real_repository.lock_read()
 
1457
            for repo in self._fallback_repositories:
 
1458
                repo.lock_read()
 
1459
        else:
 
1460
            self._lock_count += 1
 
1461
        return lock.LogicalLockResult(self.unlock)
 
1462
 
 
1463
    def _remote_lock_write(self, token):
 
1464
        path = self.bzrdir._path_for_remote_call(self._client)
 
1465
        if token is None:
 
1466
            token = ''
 
1467
        err_context = {'token': token}
 
1468
        response = self._call('Repository.lock_write', path, token,
 
1469
                              **err_context)
 
1470
        if response[0] == 'ok':
 
1471
            ok, token = response
 
1472
            return token
 
1473
        else:
 
1474
            raise errors.UnexpectedSmartServerResponse(response)
 
1475
 
 
1476
    def lock_write(self, token=None, _skip_rpc=False):
 
1477
        if not self._lock_mode:
 
1478
            self._note_lock('w')
 
1479
            if _skip_rpc:
 
1480
                if self._lock_token is not None:
 
1481
                    if token != self._lock_token:
 
1482
                        raise errors.TokenMismatch(token, self._lock_token)
 
1483
                self._lock_token = token
 
1484
            else:
 
1485
                self._lock_token = self._remote_lock_write(token)
 
1486
            # if self._lock_token is None, then this is something like packs or
 
1487
            # svn where we don't get to lock the repo, or a weave style repository
 
1488
            # where we cannot lock it over the wire and attempts to do so will
 
1489
            # fail.
 
1490
            if self._real_repository is not None:
 
1491
                self._real_repository.lock_write(token=self._lock_token)
 
1492
            if token is not None:
 
1493
                self._leave_lock = True
 
1494
            else:
 
1495
                self._leave_lock = False
 
1496
            self._lock_mode = 'w'
 
1497
            self._lock_count = 1
 
1498
            cache_misses = self._real_repository is None
 
1499
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
 
1500
            for repo in self._fallback_repositories:
 
1501
                # Writes don't affect fallback repos
 
1502
                repo.lock_read()
 
1503
        elif self._lock_mode == 'r':
 
1504
            raise errors.ReadOnlyError(self)
 
1505
        else:
 
1506
            self._lock_count += 1
 
1507
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
1508
 
 
1509
    def leave_lock_in_place(self):
 
1510
        if not self._lock_token:
 
1511
            raise NotImplementedError(self.leave_lock_in_place)
 
1512
        self._leave_lock = True
 
1513
 
 
1514
    def dont_leave_lock_in_place(self):
 
1515
        if not self._lock_token:
 
1516
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1517
        self._leave_lock = False
 
1518
 
 
1519
    def _set_real_repository(self, repository):
 
1520
        """Set the _real_repository for this repository.
 
1521
 
 
1522
        :param repository: The repository to fallback to for non-hpss
 
1523
            implemented operations.
 
1524
        """
 
1525
        if self._real_repository is not None:
 
1526
            # Replacing an already set real repository.
 
1527
            # We cannot do this [currently] if the repository is locked -
 
1528
            # synchronised state might be lost.
 
1529
            if self.is_locked():
 
1530
                raise AssertionError('_real_repository is already set')
 
1531
        if isinstance(repository, RemoteRepository):
 
1532
            raise AssertionError()
 
1533
        self._real_repository = repository
 
1534
        # three code paths happen here:
 
1535
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
 
1536
        # up stacking. In this case self._fallback_repositories is [], and the
 
1537
        # real repo is already setup. Preserve the real repo and
 
1538
        # RemoteRepository.add_fallback_repository will avoid adding
 
1539
        # duplicates.
 
1540
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
 
1541
        # ensure_real is triggered from a branch, the real repository to
 
1542
        # set already has a matching list with separate instances, but
 
1543
        # as they are also RemoteRepositories we don't worry about making the
 
1544
        # lists be identical.
 
1545
        # 3) new servers, RemoteRepository.ensure_real is triggered before
 
1546
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
 
1547
        # and need to populate it.
 
1548
        if (self._fallback_repositories and
 
1549
            len(self._real_repository._fallback_repositories) !=
 
1550
            len(self._fallback_repositories)):
 
1551
            if len(self._real_repository._fallback_repositories):
 
1552
                raise AssertionError(
 
1553
                    "cannot cleanly remove existing _fallback_repositories")
 
1554
        for fb in self._fallback_repositories:
 
1555
            self._real_repository.add_fallback_repository(fb)
 
1556
        if self._lock_mode == 'w':
 
1557
            # if we are already locked, the real repository must be able to
 
1558
            # acquire the lock with our token.
 
1559
            self._real_repository.lock_write(self._lock_token)
 
1560
        elif self._lock_mode == 'r':
 
1561
            self._real_repository.lock_read()
 
1562
        if self._write_group_tokens is not None:
 
1563
            # if we are already in a write group, resume it
 
1564
            self._real_repository.resume_write_group(self._write_group_tokens)
 
1565
            self._write_group_tokens = None
 
1566
 
 
1567
    def start_write_group(self):
 
1568
        """Start a write group on the decorated repository.
 
1569
 
 
1570
        Smart methods perform operations in a single step so this API
 
1571
        is not really applicable except as a compatibility thunk
 
1572
        for older plugins that don't use e.g. the CommitBuilder
 
1573
        facility.
 
1574
        """
 
1575
        if self._real_repository:
 
1576
            self._ensure_real()
 
1577
            return self._real_repository.start_write_group()
 
1578
        if not self.is_write_locked():
 
1579
            raise errors.NotWriteLocked(self)
 
1580
        if self._write_group_tokens is not None:
 
1581
            raise errors.BzrError('already in a write group')
 
1582
        path = self.bzrdir._path_for_remote_call(self._client)
 
1583
        try:
 
1584
            response = self._call('Repository.start_write_group', path,
 
1585
                self._lock_token)
 
1586
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
 
1587
            self._ensure_real()
 
1588
            return self._real_repository.start_write_group()
 
1589
        if response[0] != 'ok':
 
1590
            raise errors.UnexpectedSmartServerResponse(response)
 
1591
        self._write_group_tokens = response[1]
 
1592
 
 
1593
    def _unlock(self, token):
 
1594
        path = self.bzrdir._path_for_remote_call(self._client)
 
1595
        if not token:
 
1596
            # with no token the remote repository is not persistently locked.
 
1597
            return
 
1598
        err_context = {'token': token}
 
1599
        response = self._call('Repository.unlock', path, token,
 
1600
                              **err_context)
 
1601
        if response == ('ok',):
 
1602
            return
 
1603
        else:
 
1604
            raise errors.UnexpectedSmartServerResponse(response)
 
1605
 
 
1606
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
1607
    def unlock(self):
 
1608
        if not self._lock_count:
 
1609
            return lock.cant_unlock_not_held(self)
 
1610
        self._lock_count -= 1
 
1611
        if self._lock_count > 0:
 
1612
            return
 
1613
        self._unstacked_provider.disable_cache()
 
1614
        old_mode = self._lock_mode
 
1615
        self._lock_mode = None
 
1616
        try:
 
1617
            # The real repository is responsible at present for raising an
 
1618
            # exception if it's in an unfinished write group.  However, it
 
1619
            # normally will *not* actually remove the lock from disk - that's
 
1620
            # done by the server on receiving the Repository.unlock call.
 
1621
            # This is just to let the _real_repository stay up to date.
 
1622
            if self._real_repository is not None:
 
1623
                self._real_repository.unlock()
 
1624
            elif self._write_group_tokens is not None:
 
1625
                self.abort_write_group()
 
1626
        finally:
 
1627
            # The rpc-level lock should be released even if there was a
 
1628
            # problem releasing the vfs-based lock.
 
1629
            if old_mode == 'w':
 
1630
                # Only write-locked repositories need to make a remote method
 
1631
                # call to perform the unlock.
 
1632
                old_token = self._lock_token
 
1633
                self._lock_token = None
 
1634
                if not self._leave_lock:
 
1635
                    self._unlock(old_token)
 
1636
        # Fallbacks are always 'lock_read()' so we don't pay attention to
 
1637
        # self._leave_lock
 
1638
        for repo in self._fallback_repositories:
 
1639
            repo.unlock()
 
1640
 
 
1641
    def break_lock(self):
 
1642
        # should hand off to the network
 
1643
        path = self.bzrdir._path_for_remote_call(self._client)
 
1644
        try:
 
1645
            response = self._call("Repository.break_lock", path)
 
1646
        except errors.UnknownSmartMethod:
 
1647
            self._ensure_real()
 
1648
            return self._real_repository.break_lock()
 
1649
        if response != ('ok',):
 
1650
            raise errors.UnexpectedSmartServerResponse(response)
 
1651
 
 
1652
    def _get_tarball(self, compression):
 
1653
        """Return a TemporaryFile containing a repository tarball.
 
1654
 
 
1655
        Returns None if the server does not support sending tarballs.
 
1656
        """
 
1657
        import tempfile
 
1658
        path = self.bzrdir._path_for_remote_call(self._client)
 
1659
        try:
 
1660
            response, protocol = self._call_expecting_body(
 
1661
                'Repository.tarball', path, compression)
 
1662
        except errors.UnknownSmartMethod:
 
1663
            protocol.cancel_read_body()
 
1664
            return None
 
1665
        if response[0] == 'ok':
 
1666
            # Extract the tarball and return it
 
1667
            t = tempfile.NamedTemporaryFile()
 
1668
            # TODO: rpc layer should read directly into it...
 
1669
            t.write(protocol.read_body_bytes())
 
1670
            t.seek(0)
 
1671
            return t
 
1672
        raise errors.UnexpectedSmartServerResponse(response)
 
1673
 
 
1674
    @needs_read_lock
 
1675
    def sprout(self, to_bzrdir, revision_id=None):
 
1676
        """Create a descendent repository for new development.
 
1677
 
 
1678
        Unlike clone, this does not copy the settings of the repository.
 
1679
        """
 
1680
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
1681
        dest_repo.fetch(self, revision_id=revision_id)
 
1682
        return dest_repo
 
1683
 
 
1684
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
1685
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
1686
            # use target default format.
 
1687
            dest_repo = a_bzrdir.create_repository()
 
1688
        else:
 
1689
            # Most control formats need the repository to be specifically
 
1690
            # created, but on some old all-in-one formats it's not needed
 
1691
            try:
 
1692
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
 
1693
            except errors.UninitializableFormat:
 
1694
                dest_repo = a_bzrdir.open_repository()
 
1695
        return dest_repo
 
1696
 
 
1697
    ### These methods are just thin shims to the VFS object for now.
 
1698
 
 
1699
    @needs_read_lock
 
1700
    def revision_tree(self, revision_id):
 
1701
        revision_id = _mod_revision.ensure_null(revision_id)
 
1702
        if revision_id == _mod_revision.NULL_REVISION:
 
1703
            return InventoryRevisionTree(self,
 
1704
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
1705
        else:
 
1706
            return list(self.revision_trees([revision_id]))[0]
 
1707
 
 
1708
    def get_serializer_format(self):
 
1709
        path = self.bzrdir._path_for_remote_call(self._client)
 
1710
        try:
 
1711
            response = self._call('VersionedFileRepository.get_serializer_format',
 
1712
                path)
 
1713
        except errors.UnknownSmartMethod:
 
1714
            self._ensure_real()
 
1715
            return self._real_repository.get_serializer_format()
 
1716
        if response[0] != 'ok':
 
1717
            raise errors.UnexpectedSmartServerResponse(response)
 
1718
        return response[1]
 
1719
 
 
1720
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1721
                           timezone=None, committer=None, revprops=None,
 
1722
                           revision_id=None, lossy=False):
 
1723
        # FIXME: It ought to be possible to call this without immediately
 
1724
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
1725
        self._ensure_real()
 
1726
        real_repo = self._real_repository
 
1727
        builder = real_repo.get_commit_builder(branch, parents,
 
1728
                config, timestamp=timestamp, timezone=timezone,
 
1729
                committer=committer, revprops=revprops,
 
1730
                revision_id=revision_id, lossy=lossy)
 
1731
        return builder
 
1732
 
 
1733
    def add_fallback_repository(self, repository):
 
1734
        """Add a repository to use for looking up data not held locally.
 
1735
 
 
1736
        :param repository: A repository.
 
1737
        """
 
1738
        if not self._format.supports_external_lookups:
 
1739
            raise errors.UnstackableRepositoryFormat(
 
1740
                self._format.network_name(), self.base)
 
1741
        # We need to accumulate additional repositories here, to pass them in
 
1742
        # on various RPC's.
 
1743
        #
 
1744
        # Make the check before we lock: this raises an exception.
 
1745
        self._check_fallback_repository(repository)
 
1746
        if self.is_locked():
 
1747
            # We will call fallback.unlock() when we transition to the unlocked
 
1748
            # state, so always add a lock here. If a caller passes us a locked
 
1749
            # repository, they are responsible for unlocking it later.
 
1750
            repository.lock_read()
 
1751
        self._fallback_repositories.append(repository)
 
1752
        # If self._real_repository was parameterised already (e.g. because a
 
1753
        # _real_branch had its get_stacked_on_url method called), then the
 
1754
        # repository to be added may already be in the _real_repositories list.
 
1755
        if self._real_repository is not None:
 
1756
            fallback_locations = [repo.user_url for repo in
 
1757
                self._real_repository._fallback_repositories]
 
1758
            if repository.user_url not in fallback_locations:
 
1759
                self._real_repository.add_fallback_repository(repository)
 
1760
 
 
1761
    def _check_fallback_repository(self, repository):
 
1762
        """Check that this repository can fallback to repository safely.
 
1763
 
 
1764
        Raise an error if not.
 
1765
 
 
1766
        :param repository: A repository to fallback to.
 
1767
        """
 
1768
        return _mod_repository.InterRepository._assert_same_model(
 
1769
            self, repository)
 
1770
 
 
1771
    def add_inventory(self, revid, inv, parents):
 
1772
        self._ensure_real()
 
1773
        return self._real_repository.add_inventory(revid, inv, parents)
 
1774
 
 
1775
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
 
1776
            parents, basis_inv=None, propagate_caches=False):
 
1777
        self._ensure_real()
 
1778
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
 
1779
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1780
            propagate_caches=propagate_caches)
 
1781
 
 
1782
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
1783
        self._ensure_real()
 
1784
        return self._real_repository.add_revision(
 
1785
            rev_id, rev, inv=inv, config=config)
 
1786
 
 
1787
    @needs_read_lock
 
1788
    def get_inventory(self, revision_id):
 
1789
        return list(self.iter_inventories([revision_id]))[0]
 
1790
 
 
1791
    def iter_inventories(self, revision_ids, ordering=None):
 
1792
        self._ensure_real()
 
1793
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1794
 
 
1795
    @needs_read_lock
 
1796
    def get_revision(self, revision_id):
 
1797
        return self.get_revisions([revision_id])[0]
 
1798
 
 
1799
    def get_transaction(self):
 
1800
        self._ensure_real()
 
1801
        return self._real_repository.get_transaction()
 
1802
 
 
1803
    @needs_read_lock
 
1804
    def clone(self, a_bzrdir, revision_id=None):
 
1805
        dest_repo = self._create_sprouting_repo(
 
1806
            a_bzrdir, shared=self.is_shared())
 
1807
        self.copy_content_into(dest_repo, revision_id)
 
1808
        return dest_repo
 
1809
 
 
1810
    def make_working_trees(self):
 
1811
        """See Repository.make_working_trees"""
 
1812
        path = self.bzrdir._path_for_remote_call(self._client)
 
1813
        try:
 
1814
            response = self._call('Repository.make_working_trees', path)
 
1815
        except errors.UnknownSmartMethod:
 
1816
            self._ensure_real()
 
1817
            return self._real_repository.make_working_trees()
 
1818
        if response[0] not in ('yes', 'no'):
 
1819
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1820
        return response[0] == 'yes'
 
1821
 
 
1822
    def refresh_data(self):
 
1823
        """Re-read any data needed to synchronise with disk.
 
1824
 
 
1825
        This method is intended to be called after another repository instance
 
1826
        (such as one used by a smart server) has inserted data into the
 
1827
        repository. On all repositories this will work outside of write groups.
 
1828
        Some repository formats (pack and newer for bzrlib native formats)
 
1829
        support refresh_data inside write groups. If called inside a write
 
1830
        group on a repository that does not support refreshing in a write group
 
1831
        IsInWriteGroupError will be raised.
 
1832
        """
 
1833
        if self._real_repository is not None:
 
1834
            self._real_repository.refresh_data()
 
1835
 
 
1836
    def revision_ids_to_search_result(self, result_set):
 
1837
        """Convert a set of revision ids to a graph SearchResult."""
 
1838
        result_parents = set()
 
1839
        for parents in self.get_graph().get_parent_map(
 
1840
            result_set).itervalues():
 
1841
            result_parents.update(parents)
 
1842
        included_keys = result_set.intersection(result_parents)
 
1843
        start_keys = result_set.difference(included_keys)
 
1844
        exclude_keys = result_parents.difference(result_set)
 
1845
        result = vf_search.SearchResult(start_keys, exclude_keys,
 
1846
            len(result_set), result_set)
 
1847
        return result
 
1848
 
 
1849
    @needs_read_lock
 
1850
    def search_missing_revision_ids(self, other,
 
1851
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1852
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
1853
            limit=None):
 
1854
        """Return the revision ids that other has that this does not.
 
1855
 
 
1856
        These are returned in topological order.
 
1857
 
 
1858
        revision_id: only return revision ids included by revision_id.
 
1859
        """
 
1860
        if symbol_versioning.deprecated_passed(revision_id):
 
1861
            symbol_versioning.warn(
 
1862
                'search_missing_revision_ids(revision_id=...) was '
 
1863
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1864
                DeprecationWarning, stacklevel=2)
 
1865
            if revision_ids is not None:
 
1866
                raise AssertionError(
 
1867
                    'revision_ids is mutually exclusive with revision_id')
 
1868
            if revision_id is not None:
 
1869
                revision_ids = [revision_id]
 
1870
        inter_repo = _mod_repository.InterRepository.get(other, self)
 
1871
        return inter_repo.search_missing_revision_ids(
 
1872
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1873
            if_present_ids=if_present_ids, limit=limit)
 
1874
 
 
1875
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1876
            fetch_spec=None):
 
1877
        # No base implementation to use as RemoteRepository is not a subclass
 
1878
        # of Repository; so this is a copy of Repository.fetch().
 
1879
        if fetch_spec is not None and revision_id is not None:
 
1880
            raise AssertionError(
 
1881
                "fetch_spec and revision_id are mutually exclusive.")
 
1882
        if self.is_in_write_group():
 
1883
            raise errors.InternalBzrError(
 
1884
                "May not fetch while in a write group.")
 
1885
        # fast path same-url fetch operations
 
1886
        if (self.has_same_location(source)
 
1887
            and fetch_spec is None
 
1888
            and self._has_same_fallbacks(source)):
 
1889
            # check that last_revision is in 'from' and then return a
 
1890
            # no-operation.
 
1891
            if (revision_id is not None and
 
1892
                not _mod_revision.is_null(revision_id)):
 
1893
                self.get_revision(revision_id)
 
1894
            return 0, []
 
1895
        # if there is no specific appropriate InterRepository, this will get
 
1896
        # the InterRepository base class, which raises an
 
1897
        # IncompatibleRepositories when asked to fetch.
 
1898
        inter = _mod_repository.InterRepository.get(source, self)
 
1899
        if (fetch_spec is not None and
 
1900
            not getattr(inter, "supports_fetch_spec", False)):
 
1901
            raise errors.UnsupportedOperation(
 
1902
                "fetch_spec not supported for %r" % inter)
 
1903
        return inter.fetch(revision_id=revision_id,
 
1904
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1905
 
 
1906
    def create_bundle(self, target, base, fileobj, format=None):
 
1907
        self._ensure_real()
 
1908
        self._real_repository.create_bundle(target, base, fileobj, format)
 
1909
 
 
1910
    @needs_read_lock
 
1911
    @symbol_versioning.deprecated_method(
 
1912
        symbol_versioning.deprecated_in((2, 4, 0)))
 
1913
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1914
        self._ensure_real()
 
1915
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
1916
 
 
1917
    def fileids_altered_by_revision_ids(self, revision_ids):
 
1918
        self._ensure_real()
 
1919
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
1920
 
 
1921
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
 
1922
        self._ensure_real()
 
1923
        return self._real_repository._get_versioned_file_checker(
 
1924
            revisions, revision_versions_cache)
 
1925
 
 
1926
    def _iter_files_bytes_rpc(self, desired_files, absent):
 
1927
        path = self.bzrdir._path_for_remote_call(self._client)
 
1928
        lines = []
 
1929
        identifiers = []
 
1930
        for (file_id, revid, identifier) in desired_files:
 
1931
            lines.append("%s\0%s" % (
 
1932
                osutils.safe_file_id(file_id),
 
1933
                osutils.safe_revision_id(revid)))
 
1934
            identifiers.append(identifier)
 
1935
        (response_tuple, response_handler) = (
 
1936
            self._call_with_body_bytes_expecting_body(
 
1937
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
 
1938
        if response_tuple != ('ok', ):
 
1939
            response_handler.cancel_read_body()
 
1940
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1941
        byte_stream = response_handler.read_streamed_body()
 
1942
        def decompress_stream(start, byte_stream, unused):
 
1943
            decompressor = zlib.decompressobj()
 
1944
            yield decompressor.decompress(start)
 
1945
            while decompressor.unused_data == "":
 
1946
                try:
 
1947
                    data = byte_stream.next()
 
1948
                except StopIteration:
 
1949
                    break
 
1950
                yield decompressor.decompress(data)
 
1951
            yield decompressor.flush()
 
1952
            unused.append(decompressor.unused_data)
 
1953
        unused = ""
 
1954
        while True:
 
1955
            while not "\n" in unused:
 
1956
                unused += byte_stream.next()
 
1957
            header, rest = unused.split("\n", 1)
 
1958
            args = header.split("\0")
 
1959
            if args[0] == "absent":
 
1960
                absent[identifiers[int(args[3])]] = (args[1], args[2])
 
1961
                unused = rest
 
1962
                continue
 
1963
            elif args[0] == "ok":
 
1964
                idx = int(args[1])
 
1965
            else:
 
1966
                raise errors.UnexpectedSmartServerResponse(args)
 
1967
            unused_chunks = []
 
1968
            yield (identifiers[idx],
 
1969
                decompress_stream(rest, byte_stream, unused_chunks))
 
1970
            unused = "".join(unused_chunks)
 
1971
 
 
1972
    def iter_files_bytes(self, desired_files):
 
1973
        """See Repository.iter_file_bytes.
 
1974
        """
 
1975
        try:
 
1976
            absent = {}
 
1977
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
 
1978
                    desired_files, absent):
 
1979
                yield identifier, bytes_iterator
 
1980
            for fallback in self._fallback_repositories:
 
1981
                if not absent:
 
1982
                    break
 
1983
                desired_files = [(key[0], key[1], identifier) for
 
1984
                    (identifier, key) in absent.iteritems()]
 
1985
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
 
1986
                    del absent[identifier]
 
1987
                    yield identifier, bytes_iterator
 
1988
            if absent:
 
1989
                # There may be more missing items, but raise an exception
 
1990
                # for just one.
 
1991
                missing_identifier = absent.keys()[0]
 
1992
                missing_key = absent[missing_identifier]
 
1993
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
 
1994
                    file_id=missing_key[0])
 
1995
        except errors.UnknownSmartMethod:
 
1996
            self._ensure_real()
 
1997
            for (identifier, bytes_iterator) in (
 
1998
                self._real_repository.iter_files_bytes(desired_files)):
 
1999
                yield identifier, bytes_iterator
 
2000
 
 
2001
    def get_cached_parent_map(self, revision_ids):
 
2002
        """See bzrlib.CachingParentsProvider.get_cached_parent_map"""
 
2003
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
 
2004
 
 
2005
    def get_parent_map(self, revision_ids):
 
2006
        """See bzrlib.Graph.get_parent_map()."""
 
2007
        return self._make_parents_provider().get_parent_map(revision_ids)
 
2008
 
 
2009
    def _get_parent_map_rpc(self, keys):
 
2010
        """Helper for get_parent_map that performs the RPC."""
 
2011
        medium = self._client._medium
 
2012
        if medium._is_remote_before((1, 2)):
 
2013
            # We already found out that the server can't understand
 
2014
            # Repository.get_parent_map requests, so just fetch the whole
 
2015
            # graph.
 
2016
            #
 
2017
            # Note that this reads the whole graph, when only some keys are
 
2018
            # wanted.  On this old server there's no way (?) to get them all
 
2019
            # in one go, and the user probably will have seen a warning about
 
2020
            # the server being old anyhow.
 
2021
            rg = self._get_revision_graph(None)
 
2022
            # There is an API discrepancy between get_parent_map and
 
2023
            # get_revision_graph. Specifically, a "key:()" pair in
 
2024
            # get_revision_graph just means a node has no parents. For
 
2025
            # "get_parent_map" it means the node is a ghost. So fix up the
 
2026
            # graph to correct this.
 
2027
            #   https://bugs.launchpad.net/bzr/+bug/214894
 
2028
            # There is one other "bug" which is that ghosts in
 
2029
            # get_revision_graph() are not returned at all. But we won't worry
 
2030
            # about that for now.
 
2031
            for node_id, parent_ids in rg.iteritems():
 
2032
                if parent_ids == ():
 
2033
                    rg[node_id] = (NULL_REVISION,)
 
2034
            rg[NULL_REVISION] = ()
 
2035
            return rg
 
2036
 
 
2037
        keys = set(keys)
 
2038
        if None in keys:
 
2039
            raise ValueError('get_parent_map(None) is not valid')
 
2040
        if NULL_REVISION in keys:
 
2041
            keys.discard(NULL_REVISION)
 
2042
            found_parents = {NULL_REVISION:()}
 
2043
            if not keys:
 
2044
                return found_parents
 
2045
        else:
 
2046
            found_parents = {}
 
2047
        # TODO(Needs analysis): We could assume that the keys being requested
 
2048
        # from get_parent_map are in a breadth first search, so typically they
 
2049
        # will all be depth N from some common parent, and we don't have to
 
2050
        # have the server iterate from the root parent, but rather from the
 
2051
        # keys we're searching; and just tell the server the keyspace we
 
2052
        # already have; but this may be more traffic again.
 
2053
 
 
2054
        # Transform self._parents_map into a search request recipe.
 
2055
        # TODO: Manage this incrementally to avoid covering the same path
 
2056
        # repeatedly. (The server will have to on each request, but the less
 
2057
        # work done the better).
 
2058
        #
 
2059
        # Negative caching notes:
 
2060
        # new server sends missing when a request including the revid
 
2061
        # 'include-missing:' is present in the request.
 
2062
        # missing keys are serialised as missing:X, and we then call
 
2063
        # provider.note_missing(X) for-all X
 
2064
        parents_map = self._unstacked_provider.get_cached_map()
 
2065
        if parents_map is None:
 
2066
            # Repository is not locked, so there's no cache.
 
2067
            parents_map = {}
 
2068
        if _DEFAULT_SEARCH_DEPTH <= 0:
 
2069
            (start_set, stop_keys,
 
2070
             key_count) = vf_search.search_result_from_parent_map(
 
2071
                parents_map, self._unstacked_provider.missing_keys)
 
2072
        else:
 
2073
            (start_set, stop_keys,
 
2074
             key_count) = vf_search.limited_search_result_from_parent_map(
 
2075
                parents_map, self._unstacked_provider.missing_keys,
 
2076
                keys, depth=_DEFAULT_SEARCH_DEPTH)
 
2077
        recipe = ('manual', start_set, stop_keys, key_count)
 
2078
        body = self._serialise_search_recipe(recipe)
 
2079
        path = self.bzrdir._path_for_remote_call(self._client)
 
2080
        for key in keys:
 
2081
            if type(key) is not str:
 
2082
                raise ValueError(
 
2083
                    "key %r not a plain string" % (key,))
 
2084
        verb = 'Repository.get_parent_map'
 
2085
        args = (path, 'include-missing:') + tuple(keys)
 
2086
        try:
 
2087
            response = self._call_with_body_bytes_expecting_body(
 
2088
                verb, args, body)
 
2089
        except errors.UnknownSmartMethod:
 
2090
            # Server does not support this method, so get the whole graph.
 
2091
            # Worse, we have to force a disconnection, because the server now
 
2092
            # doesn't realise it has a body on the wire to consume, so the
 
2093
            # only way to recover is to abandon the connection.
 
2094
            warning(
 
2095
                'Server is too old for fast get_parent_map, reconnecting.  '
 
2096
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
2097
            medium.disconnect()
 
2098
            # To avoid having to disconnect repeatedly, we keep track of the
 
2099
            # fact the server doesn't understand remote methods added in 1.2.
 
2100
            medium._remember_remote_is_before((1, 2))
 
2101
            # Recurse just once and we should use the fallback code.
 
2102
            return self._get_parent_map_rpc(keys)
 
2103
        response_tuple, response_handler = response
 
2104
        if response_tuple[0] not in ['ok']:
 
2105
            response_handler.cancel_read_body()
 
2106
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2107
        if response_tuple[0] == 'ok':
 
2108
            coded = bz2.decompress(response_handler.read_body_bytes())
 
2109
            if coded == '':
 
2110
                # no revisions found
 
2111
                return {}
 
2112
            lines = coded.split('\n')
 
2113
            revision_graph = {}
 
2114
            for line in lines:
 
2115
                d = tuple(line.split())
 
2116
                if len(d) > 1:
 
2117
                    revision_graph[d[0]] = d[1:]
 
2118
                else:
 
2119
                    # No parents:
 
2120
                    if d[0].startswith('missing:'):
 
2121
                        revid = d[0][8:]
 
2122
                        self._unstacked_provider.note_missing_key(revid)
 
2123
                    else:
 
2124
                        # no parents - so give the Graph result
 
2125
                        # (NULL_REVISION,).
 
2126
                        revision_graph[d[0]] = (NULL_REVISION,)
 
2127
            return revision_graph
 
2128
 
 
2129
    @needs_read_lock
 
2130
    def get_signature_text(self, revision_id):
 
2131
        path = self.bzrdir._path_for_remote_call(self._client)
 
2132
        try:
 
2133
            response_tuple, response_handler = self._call_expecting_body(
 
2134
                'Repository.get_revision_signature_text', path, revision_id)
 
2135
        except errors.UnknownSmartMethod:
 
2136
            self._ensure_real()
 
2137
            return self._real_repository.get_signature_text(revision_id)
 
2138
        except errors.NoSuchRevision, err:
 
2139
            for fallback in self._fallback_repositories:
 
2140
                try:
 
2141
                    return fallback.get_signature_text(revision_id)
 
2142
                except errors.NoSuchRevision:
 
2143
                    pass
 
2144
            raise err
 
2145
        else:
 
2146
            if response_tuple[0] != 'ok':
 
2147
                raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2148
            return response_handler.read_body_bytes()
 
2149
 
 
2150
    @needs_read_lock
 
2151
    def _get_inventory_xml(self, revision_id):
 
2152
        self._ensure_real()
 
2153
        return self._real_repository._get_inventory_xml(revision_id)
 
2154
 
 
2155
    def reconcile(self, other=None, thorough=False):
 
2156
        self._ensure_real()
 
2157
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
2158
 
 
2159
    def all_revision_ids(self):
 
2160
        path = self.bzrdir._path_for_remote_call(self._client)
 
2161
        try:
 
2162
            response_tuple, response_handler = self._call_expecting_body(
 
2163
                "Repository.all_revision_ids", path)
 
2164
        except errors.UnknownSmartMethod:
 
2165
            self._ensure_real()
 
2166
            return self._real_repository.all_revision_ids()
 
2167
        if response_tuple != ("ok", ):
 
2168
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2169
        revids = set(response_handler.read_body_bytes().splitlines())
 
2170
        for fallback in self._fallback_repositories:
 
2171
            revids.update(set(fallback.all_revision_ids()))
 
2172
        return list(revids)
 
2173
 
 
2174
    @needs_read_lock
 
2175
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
2176
        self._ensure_real()
 
2177
        return self._real_repository.get_deltas_for_revisions(revisions,
 
2178
            specific_fileids=specific_fileids)
 
2179
 
 
2180
    @needs_read_lock
 
2181
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
2182
        r = self.get_revision(revision_id)
 
2183
        return list(self.get_deltas_for_revisions([r],
 
2184
            specific_fileids=specific_fileids))[0]
 
2185
 
 
2186
    @needs_read_lock
 
2187
    def revision_trees(self, revision_ids):
 
2188
        inventories = self.iter_inventories(revision_ids)
 
2189
        for inv in inventories:
 
2190
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
2191
 
 
2192
    @needs_read_lock
 
2193
    def get_revision_reconcile(self, revision_id):
 
2194
        self._ensure_real()
 
2195
        return self._real_repository.get_revision_reconcile(revision_id)
 
2196
 
 
2197
    @needs_read_lock
 
2198
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
2199
        self._ensure_real()
 
2200
        return self._real_repository.check(revision_ids=revision_ids,
 
2201
            callback_refs=callback_refs, check_repo=check_repo)
 
2202
 
 
2203
    def copy_content_into(self, destination, revision_id=None):
 
2204
        """Make a complete copy of the content in self into destination.
 
2205
 
 
2206
        This is a destructive operation! Do not use it on existing
 
2207
        repositories.
 
2208
        """
 
2209
        interrepo = _mod_repository.InterRepository.get(self, destination)
 
2210
        return interrepo.copy_content(revision_id)
 
2211
 
 
2212
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
2213
        # get a tarball of the remote repository, and copy from that into the
 
2214
        # destination
 
2215
        import tarfile
 
2216
        # TODO: Maybe a progress bar while streaming the tarball?
 
2217
        note(gettext("Copying repository content as tarball..."))
 
2218
        tar_file = self._get_tarball('bz2')
 
2219
        if tar_file is None:
 
2220
            return None
 
2221
        destination = to_bzrdir.create_repository()
 
2222
        try:
 
2223
            tar = tarfile.open('repository', fileobj=tar_file,
 
2224
                mode='r|bz2')
 
2225
            tmpdir = osutils.mkdtemp()
 
2226
            try:
 
2227
                _extract_tar(tar, tmpdir)
 
2228
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
2229
                tmp_repo = tmp_bzrdir.open_repository()
 
2230
                tmp_repo.copy_content_into(destination, revision_id)
 
2231
            finally:
 
2232
                osutils.rmtree(tmpdir)
 
2233
        finally:
 
2234
            tar_file.close()
 
2235
        return destination
 
2236
        # TODO: Suggestion from john: using external tar is much faster than
 
2237
        # python's tarfile library, but it may not work on windows.
 
2238
 
 
2239
    @property
 
2240
    def inventories(self):
 
2241
        """Decorate the real repository for now.
 
2242
 
 
2243
        In the long term a full blown network facility is needed to
 
2244
        avoid creating a real repository object locally.
 
2245
        """
 
2246
        self._ensure_real()
 
2247
        return self._real_repository.inventories
 
2248
 
 
2249
    @needs_write_lock
 
2250
    def pack(self, hint=None, clean_obsolete_packs=False):
 
2251
        """Compress the data within the repository.
 
2252
        """
 
2253
        if hint is None:
 
2254
            body = ""
 
2255
        else:
 
2256
            body = "".join([l+"\n" for l in hint])
 
2257
        path = self.bzrdir._path_for_remote_call(self._client)
 
2258
        try:
 
2259
            response, handler = self._call_with_body_bytes_expecting_body(
 
2260
                'Repository.pack', (path, self._lock_token,
 
2261
                    str(clean_obsolete_packs)), body)
 
2262
        except errors.UnknownSmartMethod:
 
2263
            self._ensure_real()
 
2264
            return self._real_repository.pack(hint=hint,
 
2265
                clean_obsolete_packs=clean_obsolete_packs)
 
2266
        handler.cancel_read_body()
 
2267
        if response != ('ok', ):
 
2268
            raise errors.UnexpectedSmartServerResponse(response)
 
2269
 
 
2270
    @property
 
2271
    def revisions(self):
 
2272
        """Decorate the real repository for now.
 
2273
 
 
2274
        In the long term a full blown network facility is needed.
 
2275
        """
 
2276
        self._ensure_real()
 
2277
        return self._real_repository.revisions
 
2278
 
 
2279
    def set_make_working_trees(self, new_value):
 
2280
        if new_value:
 
2281
            new_value_str = "True"
 
2282
        else:
 
2283
            new_value_str = "False"
 
2284
        path = self.bzrdir._path_for_remote_call(self._client)
 
2285
        try:
 
2286
            response = self._call(
 
2287
                'Repository.set_make_working_trees', path, new_value_str)
 
2288
        except errors.UnknownSmartMethod:
 
2289
            self._ensure_real()
 
2290
            self._real_repository.set_make_working_trees(new_value)
 
2291
        else:
 
2292
            if response[0] != 'ok':
 
2293
                raise errors.UnexpectedSmartServerResponse(response)
 
2294
 
 
2295
    @property
 
2296
    def signatures(self):
 
2297
        """Decorate the real repository for now.
 
2298
 
 
2299
        In the long term a full blown network facility is needed to avoid
 
2300
        creating a real repository object locally.
 
2301
        """
 
2302
        self._ensure_real()
 
2303
        return self._real_repository.signatures
 
2304
 
 
2305
    @needs_write_lock
 
2306
    def sign_revision(self, revision_id, gpg_strategy):
 
2307
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2308
        plaintext = testament.as_short_text()
 
2309
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
2310
 
 
2311
    @property
 
2312
    def texts(self):
 
2313
        """Decorate the real repository for now.
 
2314
 
 
2315
        In the long term a full blown network facility is needed to avoid
 
2316
        creating a real repository object locally.
 
2317
        """
 
2318
        self._ensure_real()
 
2319
        return self._real_repository.texts
 
2320
 
 
2321
    def _iter_revisions_rpc(self, revision_ids):
 
2322
        body = "\n".join(revision_ids)
 
2323
        path = self.bzrdir._path_for_remote_call(self._client)
 
2324
        response_tuple, response_handler = (
 
2325
            self._call_with_body_bytes_expecting_body(
 
2326
            "Repository.iter_revisions", (path, ), body))
 
2327
        if response_tuple[0] != "ok":
 
2328
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2329
        serializer_format = response_tuple[1]
 
2330
        serializer = serializer_format_registry.get(serializer_format)
 
2331
        byte_stream = response_handler.read_streamed_body()
 
2332
        decompressor = zlib.decompressobj()
 
2333
        chunks = []
 
2334
        for bytes in byte_stream:
 
2335
            chunks.append(decompressor.decompress(bytes))
 
2336
            if decompressor.unused_data != "":
 
2337
                chunks.append(decompressor.flush())
 
2338
                yield serializer.read_revision_from_string("".join(chunks))
 
2339
                unused = decompressor.unused_data
 
2340
                decompressor = zlib.decompressobj()
 
2341
                chunks = [decompressor.decompress(unused)]
 
2342
        chunks.append(decompressor.flush())
 
2343
        text = "".join(chunks)
 
2344
        if text != "":
 
2345
            yield serializer.read_revision_from_string("".join(chunks))
 
2346
 
 
2347
    @needs_read_lock
 
2348
    def get_revisions(self, revision_ids):
 
2349
        if revision_ids is None:
 
2350
            revision_ids = self.all_revision_ids()
 
2351
        else:
 
2352
            for rev_id in revision_ids:
 
2353
                if not rev_id or not isinstance(rev_id, basestring):
 
2354
                    raise errors.InvalidRevisionId(
 
2355
                        revision_id=rev_id, branch=self)
 
2356
        try:
 
2357
            missing = set(revision_ids)
 
2358
            revs = {}
 
2359
            for rev in self._iter_revisions_rpc(revision_ids):
 
2360
                missing.remove(rev.revision_id)
 
2361
                revs[rev.revision_id] = rev
 
2362
        except errors.UnknownSmartMethod:
 
2363
            self._ensure_real()
 
2364
            return self._real_repository.get_revisions(revision_ids)
 
2365
        for fallback in self._fallback_repositories:
 
2366
            if not missing:
 
2367
                break
 
2368
            for revid in list(missing):
 
2369
                # XXX JRV 2011-11-20: It would be nice if there was a
 
2370
                # public method on Repository that could be used to query
 
2371
                # for revision objects *without* failing completely if one
 
2372
                # was missing. There is VersionedFileRepository._iter_revisions,
 
2373
                # but unfortunately that's private and not provided by
 
2374
                # all repository implementations.
 
2375
                try:
 
2376
                    revs[revid] = fallback.get_revision(revid)
 
2377
                except errors.NoSuchRevision:
 
2378
                    pass
 
2379
                else:
 
2380
                    missing.remove(revid)
 
2381
        if missing:
 
2382
            raise errors.NoSuchRevision(self, list(missing)[0])
 
2383
        return [revs[revid] for revid in revision_ids]
 
2384
 
 
2385
    def supports_rich_root(self):
 
2386
        return self._format.rich_root_data
 
2387
 
 
2388
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
 
2389
    def iter_reverse_revision_history(self, revision_id):
 
2390
        self._ensure_real()
 
2391
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
2392
 
 
2393
    @property
 
2394
    def _serializer(self):
 
2395
        return self._format._serializer
 
2396
 
 
2397
    @needs_write_lock
 
2398
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
2399
        signature = gpg_strategy.sign(plaintext)
 
2400
        self.add_signature_text(revision_id, signature)
 
2401
 
 
2402
    def add_signature_text(self, revision_id, signature):
 
2403
        if self._real_repository:
 
2404
            # If there is a real repository the write group will
 
2405
            # be in the real repository as well, so use that:
 
2406
            self._ensure_real()
 
2407
            return self._real_repository.add_signature_text(
 
2408
                revision_id, signature)
 
2409
        path = self.bzrdir._path_for_remote_call(self._client)
 
2410
        response, handler = self._call_with_body_bytes_expecting_body(
 
2411
            'Repository.add_signature_text', (path, self._lock_token,
 
2412
                revision_id) + tuple(self._write_group_tokens), signature)
 
2413
        handler.cancel_read_body()
 
2414
        self.refresh_data()
 
2415
        if response[0] != 'ok':
 
2416
            raise errors.UnexpectedSmartServerResponse(response)
 
2417
        self._write_group_tokens = response[1:]
 
2418
 
 
2419
    def has_signature_for_revision_id(self, revision_id):
 
2420
        path = self.bzrdir._path_for_remote_call(self._client)
 
2421
        try:
 
2422
            response = self._call('Repository.has_signature_for_revision_id',
 
2423
                path, revision_id)
 
2424
        except errors.UnknownSmartMethod:
 
2425
            self._ensure_real()
 
2426
            return self._real_repository.has_signature_for_revision_id(
 
2427
                revision_id)
 
2428
        if response[0] not in ('yes', 'no'):
 
2429
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2430
        if response[0] == 'yes':
 
2431
            return True
 
2432
        for fallback in self._fallback_repositories:
 
2433
            if fallback.has_signature_for_revision_id(revision_id):
 
2434
                return True
 
2435
        return False
 
2436
 
 
2437
    @needs_read_lock
 
2438
    def verify_revision_signature(self, revision_id, gpg_strategy):
 
2439
        if not self.has_signature_for_revision_id(revision_id):
 
2440
            return gpg.SIGNATURE_NOT_SIGNED, None
 
2441
        signature = self.get_signature_text(revision_id)
 
2442
 
 
2443
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2444
        plaintext = testament.as_short_text()
 
2445
 
 
2446
        return gpg_strategy.verify(signature, plaintext)
 
2447
 
 
2448
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
2449
        self._ensure_real()
 
2450
        return self._real_repository.item_keys_introduced_by(revision_ids,
 
2451
            _files_pb=_files_pb)
 
2452
 
 
2453
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
 
2454
        self._ensure_real()
 
2455
        return self._real_repository._find_inconsistent_revision_parents(
 
2456
            revisions_iterator)
 
2457
 
 
2458
    def _check_for_inconsistent_revision_parents(self):
 
2459
        self._ensure_real()
 
2460
        return self._real_repository._check_for_inconsistent_revision_parents()
 
2461
 
 
2462
    def _make_parents_provider(self, other=None):
 
2463
        providers = [self._unstacked_provider]
 
2464
        if other is not None:
 
2465
            providers.insert(0, other)
 
2466
        return graph.StackedParentsProvider(_LazyListJoin(
 
2467
            providers, self._fallback_repositories))
 
2468
 
 
2469
    def _serialise_search_recipe(self, recipe):
 
2470
        """Serialise a graph search recipe.
 
2471
 
 
2472
        :param recipe: A search recipe (start, stop, count).
 
2473
        :return: Serialised bytes.
 
2474
        """
 
2475
        start_keys = ' '.join(recipe[1])
 
2476
        stop_keys = ' '.join(recipe[2])
 
2477
        count = str(recipe[3])
 
2478
        return '\n'.join((start_keys, stop_keys, count))
 
2479
 
 
2480
    def _serialise_search_result(self, search_result):
 
2481
        parts = search_result.get_network_struct()
 
2482
        return '\n'.join(parts)
 
2483
 
 
2484
    def autopack(self):
 
2485
        path = self.bzrdir._path_for_remote_call(self._client)
 
2486
        try:
 
2487
            response = self._call('PackRepository.autopack', path)
 
2488
        except errors.UnknownSmartMethod:
 
2489
            self._ensure_real()
 
2490
            self._real_repository._pack_collection.autopack()
 
2491
            return
 
2492
        self.refresh_data()
 
2493
        if response[0] != 'ok':
 
2494
            raise errors.UnexpectedSmartServerResponse(response)
 
2495
 
 
2496
 
 
2497
class RemoteStreamSink(vf_repository.StreamSink):
 
2498
 
 
2499
    def _insert_real(self, stream, src_format, resume_tokens):
 
2500
        self.target_repo._ensure_real()
 
2501
        sink = self.target_repo._real_repository._get_sink()
 
2502
        result = sink.insert_stream(stream, src_format, resume_tokens)
 
2503
        if not result:
 
2504
            self.target_repo.autopack()
 
2505
        return result
 
2506
 
 
2507
    def insert_stream(self, stream, src_format, resume_tokens):
 
2508
        target = self.target_repo
 
2509
        target._unstacked_provider.missing_keys.clear()
 
2510
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
 
2511
        if target._lock_token:
 
2512
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
2513
            lock_args = (target._lock_token or '',)
 
2514
        else:
 
2515
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
2516
            lock_args = ()
 
2517
        client = target._client
 
2518
        medium = client._medium
 
2519
        path = target.bzrdir._path_for_remote_call(client)
 
2520
        # Probe for the verb to use with an empty stream before sending the
 
2521
        # real stream to it.  We do this both to avoid the risk of sending a
 
2522
        # large request that is then rejected, and because we don't want to
 
2523
        # implement a way to buffer, rewind, or restart the stream.
 
2524
        found_verb = False
 
2525
        for verb, required_version in candidate_calls:
 
2526
            if medium._is_remote_before(required_version):
 
2527
                continue
 
2528
            if resume_tokens:
 
2529
                # We've already done the probing (and set _is_remote_before) on
 
2530
                # a previous insert.
 
2531
                found_verb = True
 
2532
                break
 
2533
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
 
2534
            try:
 
2535
                response = client.call_with_body_stream(
 
2536
                    (verb, path, '') + lock_args, byte_stream)
 
2537
            except errors.UnknownSmartMethod:
 
2538
                medium._remember_remote_is_before(required_version)
 
2539
            else:
 
2540
                found_verb = True
 
2541
                break
 
2542
        if not found_verb:
 
2543
            # Have to use VFS.
 
2544
            return self._insert_real(stream, src_format, resume_tokens)
 
2545
        self._last_inv_record = None
 
2546
        self._last_substream = None
 
2547
        if required_version < (1, 19):
 
2548
            # Remote side doesn't support inventory deltas.  Wrap the stream to
 
2549
            # make sure we don't send any.  If the stream contains inventory
 
2550
            # deltas we'll interrupt the smart insert_stream request and
 
2551
            # fallback to VFS.
 
2552
            stream = self._stop_stream_if_inventory_delta(stream)
 
2553
        byte_stream = smart_repo._stream_to_byte_stream(
 
2554
            stream, src_format)
 
2555
        resume_tokens = ' '.join(resume_tokens)
 
2556
        response = client.call_with_body_stream(
 
2557
            (verb, path, resume_tokens) + lock_args, byte_stream)
 
2558
        if response[0][0] not in ('ok', 'missing-basis'):
 
2559
            raise errors.UnexpectedSmartServerResponse(response)
 
2560
        if self._last_substream is not None:
 
2561
            # The stream included an inventory-delta record, but the remote
 
2562
            # side isn't new enough to support them.  So we need to send the
 
2563
            # rest of the stream via VFS.
 
2564
            self.target_repo.refresh_data()
 
2565
            return self._resume_stream_with_vfs(response, src_format)
 
2566
        if response[0][0] == 'missing-basis':
 
2567
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
2568
            resume_tokens = tokens
 
2569
            return resume_tokens, set(missing_keys)
 
2570
        else:
 
2571
            self.target_repo.refresh_data()
 
2572
            return [], set()
 
2573
 
 
2574
    def _resume_stream_with_vfs(self, response, src_format):
 
2575
        """Resume sending a stream via VFS, first resending the record and
 
2576
        substream that couldn't be sent via an insert_stream verb.
 
2577
        """
 
2578
        if response[0][0] == 'missing-basis':
 
2579
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
2580
            # Ignore missing_keys, we haven't finished inserting yet
 
2581
        else:
 
2582
            tokens = []
 
2583
        def resume_substream():
 
2584
            # Yield the substream that was interrupted.
 
2585
            for record in self._last_substream:
 
2586
                yield record
 
2587
            self._last_substream = None
 
2588
        def resume_stream():
 
2589
            # Finish sending the interrupted substream
 
2590
            yield ('inventory-deltas', resume_substream())
 
2591
            # Then simply continue sending the rest of the stream.
 
2592
            for substream_kind, substream in self._last_stream:
 
2593
                yield substream_kind, substream
 
2594
        return self._insert_real(resume_stream(), src_format, tokens)
 
2595
 
 
2596
    def _stop_stream_if_inventory_delta(self, stream):
 
2597
        """Normally this just lets the original stream pass-through unchanged.
 
2598
 
 
2599
        However if any 'inventory-deltas' substream occurs it will stop
 
2600
        streaming, and store the interrupted substream and stream in
 
2601
        self._last_substream and self._last_stream so that the stream can be
 
2602
        resumed by _resume_stream_with_vfs.
 
2603
        """
 
2604
 
 
2605
        stream_iter = iter(stream)
 
2606
        for substream_kind, substream in stream_iter:
 
2607
            if substream_kind == 'inventory-deltas':
 
2608
                self._last_substream = substream
 
2609
                self._last_stream = stream_iter
 
2610
                return
 
2611
            else:
 
2612
                yield substream_kind, substream
 
2613
 
 
2614
 
 
2615
class RemoteStreamSource(vf_repository.StreamSource):
 
2616
    """Stream data from a remote server."""
 
2617
 
 
2618
    def get_stream(self, search):
 
2619
        if (self.from_repository._fallback_repositories and
 
2620
            self.to_format._fetch_order == 'topological'):
 
2621
            return self._real_stream(self.from_repository, search)
 
2622
        sources = []
 
2623
        seen = set()
 
2624
        repos = [self.from_repository]
 
2625
        while repos:
 
2626
            repo = repos.pop(0)
 
2627
            if repo in seen:
 
2628
                continue
 
2629
            seen.add(repo)
 
2630
            repos.extend(repo._fallback_repositories)
 
2631
            sources.append(repo)
 
2632
        return self.missing_parents_chain(search, sources)
 
2633
 
 
2634
    def get_stream_for_missing_keys(self, missing_keys):
 
2635
        self.from_repository._ensure_real()
 
2636
        real_repo = self.from_repository._real_repository
 
2637
        real_source = real_repo._get_source(self.to_format)
 
2638
        return real_source.get_stream_for_missing_keys(missing_keys)
 
2639
 
 
2640
    def _real_stream(self, repo, search):
 
2641
        """Get a stream for search from repo.
 
2642
 
 
2643
        This never called RemoteStreamSource.get_stream, and is a helper
 
2644
        for RemoteStreamSource._get_stream to allow getting a stream
 
2645
        reliably whether fallback back because of old servers or trying
 
2646
        to stream from a non-RemoteRepository (which the stacked support
 
2647
        code will do).
 
2648
        """
 
2649
        source = repo._get_source(self.to_format)
 
2650
        if isinstance(source, RemoteStreamSource):
 
2651
            repo._ensure_real()
 
2652
            source = repo._real_repository._get_source(self.to_format)
 
2653
        return source.get_stream(search)
 
2654
 
 
2655
    def _get_stream(self, repo, search):
 
2656
        """Core worker to get a stream from repo for search.
 
2657
 
 
2658
        This is used by both get_stream and the stacking support logic. It
 
2659
        deliberately gets a stream for repo which does not need to be
 
2660
        self.from_repository. In the event that repo is not Remote, or
 
2661
        cannot do a smart stream, a fallback is made to the generic
 
2662
        repository._get_stream() interface, via self._real_stream.
 
2663
 
 
2664
        In the event of stacking, streams from _get_stream will not
 
2665
        contain all the data for search - this is normal (see get_stream).
 
2666
 
 
2667
        :param repo: A repository.
 
2668
        :param search: A search.
 
2669
        """
 
2670
        # Fallbacks may be non-smart
 
2671
        if not isinstance(repo, RemoteRepository):
 
2672
            return self._real_stream(repo, search)
 
2673
        client = repo._client
 
2674
        medium = client._medium
 
2675
        path = repo.bzrdir._path_for_remote_call(client)
 
2676
        search_bytes = repo._serialise_search_result(search)
 
2677
        args = (path, self.to_format.network_name())
 
2678
        candidate_verbs = [
 
2679
            ('Repository.get_stream_1.19', (1, 19)),
 
2680
            ('Repository.get_stream', (1, 13))]
 
2681
 
 
2682
        found_verb = False
 
2683
        for verb, version in candidate_verbs:
 
2684
            if medium._is_remote_before(version):
 
2685
                continue
 
2686
            try:
 
2687
                response = repo._call_with_body_bytes_expecting_body(
 
2688
                    verb, args, search_bytes)
 
2689
            except errors.UnknownSmartMethod:
 
2690
                medium._remember_remote_is_before(version)
 
2691
            except errors.UnknownErrorFromSmartServer, e:
 
2692
                if isinstance(search, vf_search.EverythingResult):
 
2693
                    error_verb = e.error_from_smart_server.error_verb
 
2694
                    if error_verb == 'BadSearch':
 
2695
                        # Pre-2.4 servers don't support this sort of search.
 
2696
                        # XXX: perhaps falling back to VFS on BadSearch is a
 
2697
                        # good idea in general?  It might provide a little bit
 
2698
                        # of protection against client-side bugs.
 
2699
                        medium._remember_remote_is_before((2, 4))
 
2700
                        break
 
2701
                raise
 
2702
            else:
 
2703
                response_tuple, response_handler = response
 
2704
                found_verb = True
 
2705
                break
 
2706
        if not found_verb:
 
2707
            return self._real_stream(repo, search)
 
2708
        if response_tuple[0] != 'ok':
 
2709
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2710
        byte_stream = response_handler.read_streamed_body()
 
2711
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
2712
            self._record_counter)
 
2713
        if src_format.network_name() != repo._format.network_name():
 
2714
            raise AssertionError(
 
2715
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
2716
                src_format.network_name(), repo._format.network_name()))
 
2717
        return stream
 
2718
 
 
2719
    def missing_parents_chain(self, search, sources):
 
2720
        """Chain multiple streams together to handle stacking.
 
2721
 
 
2722
        :param search: The overall search to satisfy with streams.
 
2723
        :param sources: A list of Repository objects to query.
 
2724
        """
 
2725
        self.from_serialiser = self.from_repository._format._serializer
 
2726
        self.seen_revs = set()
 
2727
        self.referenced_revs = set()
 
2728
        # If there are heads in the search, or the key count is > 0, we are not
 
2729
        # done.
 
2730
        while not search.is_empty() and len(sources) > 1:
 
2731
            source = sources.pop(0)
 
2732
            stream = self._get_stream(source, search)
 
2733
            for kind, substream in stream:
 
2734
                if kind != 'revisions':
 
2735
                    yield kind, substream
 
2736
                else:
 
2737
                    yield kind, self.missing_parents_rev_handler(substream)
 
2738
            search = search.refine(self.seen_revs, self.referenced_revs)
 
2739
            self.seen_revs = set()
 
2740
            self.referenced_revs = set()
 
2741
        if not search.is_empty():
 
2742
            for kind, stream in self._get_stream(sources[0], search):
 
2743
                yield kind, stream
 
2744
 
 
2745
    def missing_parents_rev_handler(self, substream):
 
2746
        for content in substream:
 
2747
            revision_bytes = content.get_bytes_as('fulltext')
 
2748
            revision = self.from_serialiser.read_revision_from_string(
 
2749
                revision_bytes)
 
2750
            self.seen_revs.add(content.key[-1])
 
2751
            self.referenced_revs.update(revision.parent_ids)
 
2752
            yield content
 
2753
 
 
2754
 
 
2755
class RemoteBranchLockableFiles(LockableFiles):
 
2756
    """A 'LockableFiles' implementation that talks to a smart server.
 
2757
 
 
2758
    This is not a public interface class.
 
2759
    """
 
2760
 
 
2761
    def __init__(self, bzrdir, _client):
 
2762
        self.bzrdir = bzrdir
 
2763
        self._client = _client
 
2764
        self._need_find_modes = True
 
2765
        LockableFiles.__init__(
 
2766
            self, bzrdir.get_branch_transport(None),
 
2767
            'lock', lockdir.LockDir)
 
2768
 
 
2769
    def _find_modes(self):
 
2770
        # RemoteBranches don't let the client set the mode of control files.
 
2771
        self._dir_mode = None
 
2772
        self._file_mode = None
 
2773
 
 
2774
 
 
2775
class RemoteBranchFormat(branch.BranchFormat):
 
2776
 
 
2777
    def __init__(self, network_name=None):
 
2778
        super(RemoteBranchFormat, self).__init__()
 
2779
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2780
        self._matchingbzrdir.set_branch_format(self)
 
2781
        self._custom_format = None
 
2782
        self._network_name = network_name
 
2783
 
 
2784
    def __eq__(self, other):
 
2785
        return (isinstance(other, RemoteBranchFormat) and
 
2786
            self.__dict__ == other.__dict__)
 
2787
 
 
2788
    def _ensure_real(self):
 
2789
        if self._custom_format is None:
 
2790
            try:
 
2791
                self._custom_format = branch.network_format_registry.get(
 
2792
                    self._network_name)
 
2793
            except KeyError:
 
2794
                raise errors.UnknownFormatError(kind='branch',
 
2795
                    format=self._network_name)
 
2796
 
 
2797
    def get_format_description(self):
 
2798
        self._ensure_real()
 
2799
        return 'Remote: ' + self._custom_format.get_format_description()
 
2800
 
 
2801
    def network_name(self):
 
2802
        return self._network_name
 
2803
 
 
2804
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2805
        return a_bzrdir.open_branch(name=name, 
 
2806
            ignore_fallbacks=ignore_fallbacks)
 
2807
 
 
2808
    def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
 
2809
        # Initialisation when using a local bzrdir object, or a non-vfs init
 
2810
        # method is not available on the server.
 
2811
        # self._custom_format is always set - the start of initialize ensures
 
2812
        # that.
 
2813
        if isinstance(a_bzrdir, RemoteBzrDir):
 
2814
            a_bzrdir._ensure_real()
 
2815
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2816
                name, append_revisions_only=append_revisions_only)
 
2817
        else:
 
2818
            # We assume the bzrdir is parameterised; it may not be.
 
2819
            result = self._custom_format.initialize(a_bzrdir, name,
 
2820
                append_revisions_only=append_revisions_only)
 
2821
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
2822
            not isinstance(result, RemoteBranch)):
 
2823
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
2824
                                  name=name)
 
2825
        return result
 
2826
 
 
2827
    def initialize(self, a_bzrdir, name=None, repository=None,
 
2828
                   append_revisions_only=None):
 
2829
        # 1) get the network name to use.
 
2830
        if self._custom_format:
 
2831
            network_name = self._custom_format.network_name()
 
2832
        else:
 
2833
            # Select the current bzrlib default and ask for that.
 
2834
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
 
2835
            reference_format = reference_bzrdir_format.get_branch_format()
 
2836
            self._custom_format = reference_format
 
2837
            network_name = reference_format.network_name()
 
2838
        # Being asked to create on a non RemoteBzrDir:
 
2839
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
2840
            return self._vfs_initialize(a_bzrdir, name=name,
 
2841
                append_revisions_only=append_revisions_only)
 
2842
        medium = a_bzrdir._client._medium
 
2843
        if medium._is_remote_before((1, 13)):
 
2844
            return self._vfs_initialize(a_bzrdir, name=name,
 
2845
                append_revisions_only=append_revisions_only)
 
2846
        # Creating on a remote bzr dir.
 
2847
        # 2) try direct creation via RPC
 
2848
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2849
        if name is not None:
 
2850
            # XXX JRV20100304: Support creating colocated branches
 
2851
            raise errors.NoColocatedBranchSupport(self)
 
2852
        verb = 'BzrDir.create_branch'
 
2853
        try:
 
2854
            response = a_bzrdir._call(verb, path, network_name)
 
2855
        except errors.UnknownSmartMethod:
 
2856
            # Fallback - use vfs methods
 
2857
            medium._remember_remote_is_before((1, 13))
 
2858
            return self._vfs_initialize(a_bzrdir, name=name,
 
2859
                    append_revisions_only=append_revisions_only)
 
2860
        if response[0] != 'ok':
 
2861
            raise errors.UnexpectedSmartServerResponse(response)
 
2862
        # Turn the response into a RemoteRepository object.
 
2863
        format = RemoteBranchFormat(network_name=response[1])
 
2864
        repo_format = response_tuple_to_repo_format(response[3:])
 
2865
        repo_path = response[2]
 
2866
        if repository is not None:
 
2867
            remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
 
2868
            url_diff = urlutils.relative_url(repository.user_url,
 
2869
                    remote_repo_url)
 
2870
            if url_diff != '.':
 
2871
                raise AssertionError(
 
2872
                    'repository.user_url %r does not match URL from server '
 
2873
                    'response (%r + %r)'
 
2874
                    % (repository.user_url, a_bzrdir.user_url, repo_path))
 
2875
            remote_repo = repository
 
2876
        else:
 
2877
            if repo_path == '':
 
2878
                repo_bzrdir = a_bzrdir
 
2879
            else:
 
2880
                repo_bzrdir = RemoteBzrDir(
 
2881
                    a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
 
2882
                    a_bzrdir._client)
 
2883
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2884
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2885
            format=format, setup_stacking=False, name=name)
 
2886
        if append_revisions_only:
 
2887
            remote_branch.set_append_revisions_only(append_revisions_only)
 
2888
        # XXX: We know this is a new branch, so it must have revno 0, revid
 
2889
        # NULL_REVISION. Creating the branch locked would make this be unable
 
2890
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
 
2891
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
 
2892
        return remote_branch
 
2893
 
 
2894
    def make_tags(self, branch):
 
2895
        self._ensure_real()
 
2896
        return self._custom_format.make_tags(branch)
 
2897
 
 
2898
    def supports_tags(self):
 
2899
        # Remote branches might support tags, but we won't know until we
 
2900
        # access the real remote branch.
 
2901
        self._ensure_real()
 
2902
        return self._custom_format.supports_tags()
 
2903
 
 
2904
    def supports_stacking(self):
 
2905
        self._ensure_real()
 
2906
        return self._custom_format.supports_stacking()
 
2907
 
 
2908
    def supports_set_append_revisions_only(self):
 
2909
        self._ensure_real()
 
2910
        return self._custom_format.supports_set_append_revisions_only()
 
2911
 
 
2912
    def _use_default_local_heads_to_fetch(self):
 
2913
        # If the branch format is a metadir format *and* its heads_to_fetch
 
2914
        # implementation is not overridden vs the base class, we can use the
 
2915
        # base class logic rather than use the heads_to_fetch RPC.  This is
 
2916
        # usually cheaper in terms of net round trips, as the last-revision and
 
2917
        # tags info fetched is cached and would be fetched anyway.
 
2918
        self._ensure_real()
 
2919
        if isinstance(self._custom_format, branch.BranchFormatMetadir):
 
2920
            branch_class = self._custom_format._branch_class()
 
2921
            heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
 
2922
            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
 
2923
                return True
 
2924
        return False
 
2925
 
 
2926
 
 
2927
class RemoteBranchStore(config.IniFileStore):
 
2928
    """Branch store which attempts to use HPSS calls to retrieve branch store.
 
2929
 
 
2930
    Note that this is specific to bzr-based formats.
 
2931
    """
 
2932
 
 
2933
    def __init__(self, branch):
 
2934
        super(RemoteBranchStore, self).__init__()
 
2935
        self.branch = branch
 
2936
        self.id = "branch"
 
2937
        self._real_store = None
 
2938
 
 
2939
    def lock_write(self, token=None):
 
2940
        return self.branch.lock_write(token)
 
2941
 
 
2942
    def unlock(self):
 
2943
        return self.branch.unlock()
 
2944
 
 
2945
    @needs_write_lock
 
2946
    def save(self):
 
2947
        # We need to be able to override the undecorated implementation
 
2948
        self.save_without_locking()
 
2949
 
 
2950
    def save_without_locking(self):
 
2951
        super(RemoteBranchStore, self).save()
 
2952
 
 
2953
    def external_url(self):
 
2954
        return self.branch.user_url
 
2955
 
 
2956
    def _load_content(self):
 
2957
        path = self.branch._remote_path()
 
2958
        try:
 
2959
            response, handler = self.branch._call_expecting_body(
 
2960
                'Branch.get_config_file', path)
 
2961
        except errors.UnknownSmartMethod:
 
2962
            self._ensure_real()
 
2963
            return self._real_store._load_content()
 
2964
        if len(response) and response[0] != 'ok':
 
2965
            raise errors.UnexpectedSmartServerResponse(response)
 
2966
        return handler.read_body_bytes()
 
2967
 
 
2968
    def _save_content(self, content):
 
2969
        path = self.branch._remote_path()
 
2970
        try:
 
2971
            response, handler = self.branch._call_with_body_bytes_expecting_body(
 
2972
                'Branch.put_config_file', (path,
 
2973
                    self.branch._lock_token, self.branch._repo_lock_token),
 
2974
                content)
 
2975
        except errors.UnknownSmartMethod:
 
2976
            self._ensure_real()
 
2977
            return self._real_store._save_content(content)
 
2978
        handler.cancel_read_body()
 
2979
        if response != ('ok', ):
 
2980
            raise errors.UnexpectedSmartServerResponse(response)
 
2981
 
 
2982
    def _ensure_real(self):
 
2983
        self.branch._ensure_real()
 
2984
        if self._real_store is None:
 
2985
            self._real_store = config.BranchStore(self.branch)
 
2986
 
 
2987
 
 
2988
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
 
2989
    """Branch stored on a server accessed by HPSS RPC.
 
2990
 
 
2991
    At the moment most operations are mapped down to simple file operations.
 
2992
    """
 
2993
 
 
2994
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
2995
        _client=None, format=None, setup_stacking=True, name=None,
 
2996
        possible_transports=None):
 
2997
        """Create a RemoteBranch instance.
 
2998
 
 
2999
        :param real_branch: An optional local implementation of the branch
 
3000
            format, usually accessing the data via the VFS.
 
3001
        :param _client: Private parameter for testing.
 
3002
        :param format: A RemoteBranchFormat object, None to create one
 
3003
            automatically. If supplied it should have a network_name already
 
3004
            supplied.
 
3005
        :param setup_stacking: If True make an RPC call to determine the
 
3006
            stacked (or not) status of the branch. If False assume the branch
 
3007
            is not stacked.
 
3008
        :param name: Colocated branch name
 
3009
        """
 
3010
        # We intentionally don't call the parent class's __init__, because it
 
3011
        # will try to assign to self.tags, which is a property in this subclass.
 
3012
        # And the parent's __init__ doesn't do much anyway.
 
3013
        self.bzrdir = remote_bzrdir
 
3014
        if _client is not None:
 
3015
            self._client = _client
 
3016
        else:
 
3017
            self._client = remote_bzrdir._client
 
3018
        self.repository = remote_repository
 
3019
        if real_branch is not None:
 
3020
            self._real_branch = real_branch
 
3021
            # Give the remote repository the matching real repo.
 
3022
            real_repo = self._real_branch.repository
 
3023
            if isinstance(real_repo, RemoteRepository):
 
3024
                real_repo._ensure_real()
 
3025
                real_repo = real_repo._real_repository
 
3026
            self.repository._set_real_repository(real_repo)
 
3027
            # Give the branch the remote repository to let fast-pathing happen.
 
3028
            self._real_branch.repository = self.repository
 
3029
        else:
 
3030
            self._real_branch = None
 
3031
        # Fill out expected attributes of branch for bzrlib API users.
 
3032
        self._clear_cached_state()
 
3033
        # TODO: deprecate self.base in favor of user_url
 
3034
        self.base = self.bzrdir.user_url
 
3035
        self._name = name
 
3036
        self._control_files = None
 
3037
        self._lock_mode = None
 
3038
        self._lock_token = None
 
3039
        self._repo_lock_token = None
 
3040
        self._lock_count = 0
 
3041
        self._leave_lock = False
 
3042
        # Setup a format: note that we cannot call _ensure_real until all the
 
3043
        # attributes above are set: This code cannot be moved higher up in this
 
3044
        # function.
 
3045
        if format is None:
 
3046
            self._format = RemoteBranchFormat()
 
3047
            if real_branch is not None:
 
3048
                self._format._network_name = \
 
3049
                    self._real_branch._format.network_name()
 
3050
        else:
 
3051
            self._format = format
 
3052
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
 
3053
        # branch.open_branch method.
 
3054
        self._real_ignore_fallbacks = not setup_stacking
 
3055
        if not self._format._network_name:
 
3056
            # Did not get from open_branchV2 - old server.
 
3057
            self._ensure_real()
 
3058
            self._format._network_name = \
 
3059
                self._real_branch._format.network_name()
 
3060
        self.tags = self._format.make_tags(self)
 
3061
        # The base class init is not called, so we duplicate this:
 
3062
        hooks = branch.Branch.hooks['open']
 
3063
        for hook in hooks:
 
3064
            hook(self)
 
3065
        self._is_stacked = False
 
3066
        if setup_stacking:
 
3067
            self._setup_stacking(possible_transports)
 
3068
 
 
3069
    def _setup_stacking(self, possible_transports):
 
3070
        # configure stacking into the remote repository, by reading it from
 
3071
        # the vfs branch.
 
3072
        try:
 
3073
            fallback_url = self.get_stacked_on_url()
 
3074
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
3075
            errors.UnstackableRepositoryFormat), e:
 
3076
            return
 
3077
        self._is_stacked = True
 
3078
        if possible_transports is None:
 
3079
            possible_transports = []
 
3080
        else:
 
3081
            possible_transports = list(possible_transports)
 
3082
        possible_transports.append(self.bzrdir.root_transport)
 
3083
        self._activate_fallback_location(fallback_url,
 
3084
            possible_transports=possible_transports)
 
3085
 
 
3086
    def _get_config(self):
 
3087
        return RemoteBranchConfig(self)
 
3088
 
 
3089
    def _get_config_store(self):
 
3090
        return RemoteBranchStore(self)
 
3091
 
 
3092
    def _get_real_transport(self):
 
3093
        # if we try vfs access, return the real branch's vfs transport
 
3094
        self._ensure_real()
 
3095
        return self._real_branch._transport
 
3096
 
 
3097
    _transport = property(_get_real_transport)
 
3098
 
 
3099
    def __str__(self):
 
3100
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
3101
 
 
3102
    __repr__ = __str__
 
3103
 
 
3104
    def _ensure_real(self):
 
3105
        """Ensure that there is a _real_branch set.
 
3106
 
 
3107
        Used before calls to self._real_branch.
 
3108
        """
 
3109
        if self._real_branch is None:
 
3110
            if not vfs.vfs_enabled():
 
3111
                raise AssertionError('smart server vfs must be enabled '
 
3112
                    'to use vfs implementation')
 
3113
            self.bzrdir._ensure_real()
 
3114
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
3115
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
3116
            if self.repository._real_repository is None:
 
3117
                # Give the remote repository the matching real repo.
 
3118
                real_repo = self._real_branch.repository
 
3119
                if isinstance(real_repo, RemoteRepository):
 
3120
                    real_repo._ensure_real()
 
3121
                    real_repo = real_repo._real_repository
 
3122
                self.repository._set_real_repository(real_repo)
 
3123
            # Give the real branch the remote repository to let fast-pathing
 
3124
            # happen.
 
3125
            self._real_branch.repository = self.repository
 
3126
            if self._lock_mode == 'r':
 
3127
                self._real_branch.lock_read()
 
3128
            elif self._lock_mode == 'w':
 
3129
                self._real_branch.lock_write(token=self._lock_token)
 
3130
 
 
3131
    def _translate_error(self, err, **context):
 
3132
        self.repository._translate_error(err, branch=self, **context)
 
3133
 
 
3134
    def _clear_cached_state(self):
 
3135
        super(RemoteBranch, self)._clear_cached_state()
 
3136
        if self._real_branch is not None:
 
3137
            self._real_branch._clear_cached_state()
 
3138
 
 
3139
    def _clear_cached_state_of_remote_branch_only(self):
 
3140
        """Like _clear_cached_state, but doesn't clear the cache of
 
3141
        self._real_branch.
 
3142
 
 
3143
        This is useful when falling back to calling a method of
 
3144
        self._real_branch that changes state.  In that case the underlying
 
3145
        branch changes, so we need to invalidate this RemoteBranch's cache of
 
3146
        it.  However, there's no need to invalidate the _real_branch's cache
 
3147
        too, in fact doing so might harm performance.
 
3148
        """
 
3149
        super(RemoteBranch, self)._clear_cached_state()
 
3150
 
 
3151
    @property
 
3152
    def control_files(self):
 
3153
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
3154
        # because it triggers an _ensure_real that we otherwise might not need.
 
3155
        if self._control_files is None:
 
3156
            self._control_files = RemoteBranchLockableFiles(
 
3157
                self.bzrdir, self._client)
 
3158
        return self._control_files
 
3159
 
 
3160
    def _get_checkout_format(self, lightweight=False):
 
3161
        self._ensure_real()
 
3162
        if lightweight:
 
3163
            format = RemoteBzrDirFormat()
 
3164
            self.bzrdir._format._supply_sub_formats_to(format)
 
3165
            format.workingtree_format = self._real_branch._get_checkout_format(
 
3166
                lightweight=lightweight).workingtree_format
 
3167
            return format
 
3168
        else:
 
3169
            return self._real_branch._get_checkout_format(lightweight=False)
 
3170
 
 
3171
    def get_physical_lock_status(self):
 
3172
        """See Branch.get_physical_lock_status()."""
 
3173
        try:
 
3174
            response = self._client.call('Branch.get_physical_lock_status',
 
3175
                self._remote_path())
 
3176
        except errors.UnknownSmartMethod:
 
3177
            self._ensure_real()
 
3178
            return self._real_branch.get_physical_lock_status()
 
3179
        if response[0] not in ('yes', 'no'):
 
3180
            raise errors.UnexpectedSmartServerResponse(response)
 
3181
        return (response[0] == 'yes')
 
3182
 
 
3183
    def get_stacked_on_url(self):
 
3184
        """Get the URL this branch is stacked against.
 
3185
 
 
3186
        :raises NotStacked: If the branch is not stacked.
 
3187
        :raises UnstackableBranchFormat: If the branch does not support
 
3188
            stacking.
 
3189
        :raises UnstackableRepositoryFormat: If the repository does not support
 
3190
            stacking.
 
3191
        """
 
3192
        try:
 
3193
            # there may not be a repository yet, so we can't use
 
3194
            # self._translate_error, so we can't use self._call either.
 
3195
            response = self._client.call('Branch.get_stacked_on_url',
 
3196
                self._remote_path())
 
3197
        except errors.ErrorFromSmartServer, err:
 
3198
            # there may not be a repository yet, so we can't call through
 
3199
            # its _translate_error
 
3200
            _translate_error(err, branch=self)
 
3201
        except errors.UnknownSmartMethod, err:
 
3202
            self._ensure_real()
 
3203
            return self._real_branch.get_stacked_on_url()
 
3204
        if response[0] != 'ok':
 
3205
            raise errors.UnexpectedSmartServerResponse(response)
 
3206
        return response[1]
 
3207
 
 
3208
    def set_stacked_on_url(self, url):
 
3209
        branch.Branch.set_stacked_on_url(self, url)
 
3210
        if not url:
 
3211
            self._is_stacked = False
 
3212
        else:
 
3213
            self._is_stacked = True
 
3214
 
 
3215
    def _vfs_get_tags_bytes(self):
 
3216
        self._ensure_real()
 
3217
        return self._real_branch._get_tags_bytes()
 
3218
 
 
3219
    @needs_read_lock
 
3220
    def _get_tags_bytes(self):
 
3221
        if self._tags_bytes is None:
 
3222
            self._tags_bytes = self._get_tags_bytes_via_hpss()
 
3223
        return self._tags_bytes
 
3224
 
 
3225
    def _get_tags_bytes_via_hpss(self):
 
3226
        medium = self._client._medium
 
3227
        if medium._is_remote_before((1, 13)):
 
3228
            return self._vfs_get_tags_bytes()
 
3229
        try:
 
3230
            response = self._call('Branch.get_tags_bytes', self._remote_path())
 
3231
        except errors.UnknownSmartMethod:
 
3232
            medium._remember_remote_is_before((1, 13))
 
3233
            return self._vfs_get_tags_bytes()
 
3234
        return response[0]
 
3235
 
 
3236
    def _vfs_set_tags_bytes(self, bytes):
 
3237
        self._ensure_real()
 
3238
        return self._real_branch._set_tags_bytes(bytes)
 
3239
 
 
3240
    def _set_tags_bytes(self, bytes):
 
3241
        if self.is_locked():
 
3242
            self._tags_bytes = bytes
 
3243
        medium = self._client._medium
 
3244
        if medium._is_remote_before((1, 18)):
 
3245
            self._vfs_set_tags_bytes(bytes)
 
3246
            return
 
3247
        try:
 
3248
            args = (
 
3249
                self._remote_path(), self._lock_token, self._repo_lock_token)
 
3250
            response = self._call_with_body_bytes(
 
3251
                'Branch.set_tags_bytes', args, bytes)
 
3252
        except errors.UnknownSmartMethod:
 
3253
            medium._remember_remote_is_before((1, 18))
 
3254
            self._vfs_set_tags_bytes(bytes)
 
3255
 
 
3256
    def lock_read(self):
 
3257
        """Lock the branch for read operations.
 
3258
 
 
3259
        :return: A bzrlib.lock.LogicalLockResult.
 
3260
        """
 
3261
        self.repository.lock_read()
 
3262
        if not self._lock_mode:
 
3263
            self._note_lock('r')
 
3264
            self._lock_mode = 'r'
 
3265
            self._lock_count = 1
 
3266
            if self._real_branch is not None:
 
3267
                self._real_branch.lock_read()
 
3268
        else:
 
3269
            self._lock_count += 1
 
3270
        return lock.LogicalLockResult(self.unlock)
 
3271
 
 
3272
    def _remote_lock_write(self, token):
 
3273
        if token is None:
 
3274
            branch_token = repo_token = ''
 
3275
        else:
 
3276
            branch_token = token
 
3277
            repo_token = self.repository.lock_write().repository_token
 
3278
            self.repository.unlock()
 
3279
        err_context = {'token': token}
 
3280
        try:
 
3281
            response = self._call(
 
3282
                'Branch.lock_write', self._remote_path(), branch_token,
 
3283
                repo_token or '', **err_context)
 
3284
        except errors.LockContention, e:
 
3285
            # The LockContention from the server doesn't have any
 
3286
            # information about the lock_url. We re-raise LockContention
 
3287
            # with valid lock_url.
 
3288
            raise errors.LockContention('(remote lock)',
 
3289
                self.repository.base.split('.bzr/')[0])
 
3290
        if response[0] != 'ok':
 
3291
            raise errors.UnexpectedSmartServerResponse(response)
 
3292
        ok, branch_token, repo_token = response
 
3293
        return branch_token, repo_token
 
3294
 
 
3295
    def lock_write(self, token=None):
 
3296
        if not self._lock_mode:
 
3297
            self._note_lock('w')
 
3298
            # Lock the branch and repo in one remote call.
 
3299
            remote_tokens = self._remote_lock_write(token)
 
3300
            self._lock_token, self._repo_lock_token = remote_tokens
 
3301
            if not self._lock_token:
 
3302
                raise SmartProtocolError('Remote server did not return a token!')
 
3303
            # Tell the self.repository object that it is locked.
 
3304
            self.repository.lock_write(
 
3305
                self._repo_lock_token, _skip_rpc=True)
 
3306
 
 
3307
            if self._real_branch is not None:
 
3308
                self._real_branch.lock_write(token=self._lock_token)
 
3309
            if token is not None:
 
3310
                self._leave_lock = True
 
3311
            else:
 
3312
                self._leave_lock = False
 
3313
            self._lock_mode = 'w'
 
3314
            self._lock_count = 1
 
3315
        elif self._lock_mode == 'r':
 
3316
            raise errors.ReadOnlyError(self)
 
3317
        else:
 
3318
            if token is not None:
 
3319
                # A token was given to lock_write, and we're relocking, so
 
3320
                # check that the given token actually matches the one we
 
3321
                # already have.
 
3322
                if token != self._lock_token:
 
3323
                    raise errors.TokenMismatch(token, self._lock_token)
 
3324
            self._lock_count += 1
 
3325
            # Re-lock the repository too.
 
3326
            self.repository.lock_write(self._repo_lock_token)
 
3327
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
3328
 
 
3329
    def _unlock(self, branch_token, repo_token):
 
3330
        err_context = {'token': str((branch_token, repo_token))}
 
3331
        response = self._call(
 
3332
            'Branch.unlock', self._remote_path(), branch_token,
 
3333
            repo_token or '', **err_context)
 
3334
        if response == ('ok',):
 
3335
            return
 
3336
        raise errors.UnexpectedSmartServerResponse(response)
 
3337
 
 
3338
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
3339
    def unlock(self):
 
3340
        try:
 
3341
            self._lock_count -= 1
 
3342
            if not self._lock_count:
 
3343
                self._clear_cached_state()
 
3344
                mode = self._lock_mode
 
3345
                self._lock_mode = None
 
3346
                if self._real_branch is not None:
 
3347
                    if (not self._leave_lock and mode == 'w' and
 
3348
                        self._repo_lock_token):
 
3349
                        # If this RemoteBranch will remove the physical lock
 
3350
                        # for the repository, make sure the _real_branch
 
3351
                        # doesn't do it first.  (Because the _real_branch's
 
3352
                        # repository is set to be the RemoteRepository.)
 
3353
                        self._real_branch.repository.leave_lock_in_place()
 
3354
                    self._real_branch.unlock()
 
3355
                if mode != 'w':
 
3356
                    # Only write-locked branched need to make a remote method
 
3357
                    # call to perform the unlock.
 
3358
                    return
 
3359
                if not self._lock_token:
 
3360
                    raise AssertionError('Locked, but no token!')
 
3361
                branch_token = self._lock_token
 
3362
                repo_token = self._repo_lock_token
 
3363
                self._lock_token = None
 
3364
                self._repo_lock_token = None
 
3365
                if not self._leave_lock:
 
3366
                    self._unlock(branch_token, repo_token)
 
3367
        finally:
 
3368
            self.repository.unlock()
 
3369
 
 
3370
    def break_lock(self):
 
3371
        try:
 
3372
            response = self._call(
 
3373
                'Branch.break_lock', self._remote_path())
 
3374
        except errors.UnknownSmartMethod:
 
3375
            self._ensure_real()
 
3376
            return self._real_branch.break_lock()
 
3377
        if response != ('ok',):
 
3378
            raise errors.UnexpectedSmartServerResponse(response)
 
3379
 
 
3380
    def leave_lock_in_place(self):
 
3381
        if not self._lock_token:
 
3382
            raise NotImplementedError(self.leave_lock_in_place)
 
3383
        self._leave_lock = True
 
3384
 
 
3385
    def dont_leave_lock_in_place(self):
 
3386
        if not self._lock_token:
 
3387
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
3388
        self._leave_lock = False
 
3389
 
 
3390
    @needs_read_lock
 
3391
    def get_rev_id(self, revno, history=None):
 
3392
        if revno == 0:
 
3393
            return _mod_revision.NULL_REVISION
 
3394
        last_revision_info = self.last_revision_info()
 
3395
        ok, result = self.repository.get_rev_id_for_revno(
 
3396
            revno, last_revision_info)
 
3397
        if ok:
 
3398
            return result
 
3399
        missing_parent = result[1]
 
3400
        # Either the revision named by the server is missing, or its parent
 
3401
        # is.  Call get_parent_map to determine which, so that we report a
 
3402
        # useful error.
 
3403
        parent_map = self.repository.get_parent_map([missing_parent])
 
3404
        if missing_parent in parent_map:
 
3405
            missing_parent = parent_map[missing_parent]
 
3406
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
3407
 
 
3408
    def _read_last_revision_info(self):
 
3409
        response = self._call('Branch.last_revision_info', self._remote_path())
 
3410
        if response[0] != 'ok':
 
3411
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
3412
        revno = int(response[1])
 
3413
        last_revision = response[2]
 
3414
        return (revno, last_revision)
 
3415
 
 
3416
    def _gen_revision_history(self):
 
3417
        """See Branch._gen_revision_history()."""
 
3418
        if self._is_stacked:
 
3419
            self._ensure_real()
 
3420
            return self._real_branch._gen_revision_history()
 
3421
        response_tuple, response_handler = self._call_expecting_body(
 
3422
            'Branch.revision_history', self._remote_path())
 
3423
        if response_tuple[0] != 'ok':
 
3424
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
3425
        result = response_handler.read_body_bytes().split('\x00')
 
3426
        if result == ['']:
 
3427
            return []
 
3428
        return result
 
3429
 
 
3430
    def _remote_path(self):
 
3431
        return self.bzrdir._path_for_remote_call(self._client)
 
3432
 
 
3433
    def _set_last_revision_descendant(self, revision_id, other_branch,
 
3434
            allow_diverged=False, allow_overwrite_descendant=False):
 
3435
        # This performs additional work to meet the hook contract; while its
 
3436
        # undesirable, we have to synthesise the revno to call the hook, and
 
3437
        # not calling the hook is worse as it means changes can't be prevented.
 
3438
        # Having calculated this though, we can't just call into
 
3439
        # set_last_revision_info as a simple call, because there is a set_rh
 
3440
        # hook that some folk may still be using.
 
3441
        old_revno, old_revid = self.last_revision_info()
 
3442
        history = self._lefthand_history(revision_id)
 
3443
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
3444
        err_context = {'other_branch': other_branch}
 
3445
        response = self._call('Branch.set_last_revision_ex',
 
3446
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
3447
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
3448
            **err_context)
 
3449
        self._clear_cached_state()
 
3450
        if len(response) != 3 and response[0] != 'ok':
 
3451
            raise errors.UnexpectedSmartServerResponse(response)
 
3452
        new_revno, new_revision_id = response[1:]
 
3453
        self._last_revision_info_cache = new_revno, new_revision_id
 
3454
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
3455
        if self._real_branch is not None:
 
3456
            cache = new_revno, new_revision_id
 
3457
            self._real_branch._last_revision_info_cache = cache
 
3458
 
 
3459
    def _set_last_revision(self, revision_id):
 
3460
        old_revno, old_revid = self.last_revision_info()
 
3461
        # This performs additional work to meet the hook contract; while its
 
3462
        # undesirable, we have to synthesise the revno to call the hook, and
 
3463
        # not calling the hook is worse as it means changes can't be prevented.
 
3464
        # Having calculated this though, we can't just call into
 
3465
        # set_last_revision_info as a simple call, because there is a set_rh
 
3466
        # hook that some folk may still be using.
 
3467
        history = self._lefthand_history(revision_id)
 
3468
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
3469
        self._clear_cached_state()
 
3470
        response = self._call('Branch.set_last_revision',
 
3471
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
3472
            revision_id)
 
3473
        if response != ('ok',):
 
3474
            raise errors.UnexpectedSmartServerResponse(response)
 
3475
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
3476
 
 
3477
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
 
3478
    @needs_write_lock
 
3479
    def set_revision_history(self, rev_history):
 
3480
        """See Branch.set_revision_history."""
 
3481
        self._set_revision_history(rev_history)
 
3482
 
 
3483
    @needs_write_lock
 
3484
    def _set_revision_history(self, rev_history):
 
3485
        # Send just the tip revision of the history; the server will generate
 
3486
        # the full history from that.  If the revision doesn't exist in this
 
3487
        # branch, NoSuchRevision will be raised.
 
3488
        if rev_history == []:
 
3489
            rev_id = 'null:'
 
3490
        else:
 
3491
            rev_id = rev_history[-1]
 
3492
        self._set_last_revision(rev_id)
 
3493
        for hook in branch.Branch.hooks['set_rh']:
 
3494
            hook(self, rev_history)
 
3495
        self._cache_revision_history(rev_history)
 
3496
 
 
3497
    def _get_parent_location(self):
 
3498
        medium = self._client._medium
 
3499
        if medium._is_remote_before((1, 13)):
 
3500
            return self._vfs_get_parent_location()
 
3501
        try:
 
3502
            response = self._call('Branch.get_parent', self._remote_path())
 
3503
        except errors.UnknownSmartMethod:
 
3504
            medium._remember_remote_is_before((1, 13))
 
3505
            return self._vfs_get_parent_location()
 
3506
        if len(response) != 1:
 
3507
            raise errors.UnexpectedSmartServerResponse(response)
 
3508
        parent_location = response[0]
 
3509
        if parent_location == '':
 
3510
            return None
 
3511
        return parent_location
 
3512
 
 
3513
    def _vfs_get_parent_location(self):
 
3514
        self._ensure_real()
 
3515
        return self._real_branch._get_parent_location()
 
3516
 
 
3517
    def _set_parent_location(self, url):
 
3518
        medium = self._client._medium
 
3519
        if medium._is_remote_before((1, 15)):
 
3520
            return self._vfs_set_parent_location(url)
 
3521
        try:
 
3522
            call_url = url or ''
 
3523
            if type(call_url) is not str:
 
3524
                raise AssertionError('url must be a str or None (%s)' % url)
 
3525
            response = self._call('Branch.set_parent_location',
 
3526
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
3527
                call_url)
 
3528
        except errors.UnknownSmartMethod:
 
3529
            medium._remember_remote_is_before((1, 15))
 
3530
            return self._vfs_set_parent_location(url)
 
3531
        if response != ():
 
3532
            raise errors.UnexpectedSmartServerResponse(response)
 
3533
 
 
3534
    def _vfs_set_parent_location(self, url):
 
3535
        self._ensure_real()
 
3536
        return self._real_branch._set_parent_location(url)
 
3537
 
 
3538
    @needs_write_lock
 
3539
    def pull(self, source, overwrite=False, stop_revision=None,
 
3540
             **kwargs):
 
3541
        self._clear_cached_state_of_remote_branch_only()
 
3542
        self._ensure_real()
 
3543
        return self._real_branch.pull(
 
3544
            source, overwrite=overwrite, stop_revision=stop_revision,
 
3545
            _override_hook_target=self, **kwargs)
 
3546
 
 
3547
    @needs_read_lock
 
3548
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
 
3549
        self._ensure_real()
 
3550
        return self._real_branch.push(
 
3551
            target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
 
3552
            _override_hook_source_branch=self)
 
3553
 
 
3554
    def is_locked(self):
 
3555
        return self._lock_count >= 1
 
3556
 
 
3557
    @needs_read_lock
 
3558
    def revision_id_to_dotted_revno(self, revision_id):
 
3559
        """Given a revision id, return its dotted revno.
 
3560
 
 
3561
        :return: a tuple like (1,) or (400,1,3).
 
3562
        """
 
3563
        try:
 
3564
            response = self._call('Branch.revision_id_to_revno',
 
3565
                self._remote_path(), revision_id)
 
3566
        except errors.UnknownSmartMethod:
 
3567
            self._ensure_real()
 
3568
            return self._real_branch.revision_id_to_dotted_revno(revision_id)
 
3569
        if response[0] == 'ok':
 
3570
            return tuple([int(x) for x in response[1:]])
 
3571
        else:
 
3572
            raise errors.UnexpectedSmartServerResponse(response)
 
3573
 
 
3574
    @needs_read_lock
 
3575
    def revision_id_to_revno(self, revision_id):
 
3576
        """Given a revision id on the branch mainline, return its revno.
 
3577
 
 
3578
        :return: an integer
 
3579
        """
 
3580
        try:
 
3581
            response = self._call('Branch.revision_id_to_revno',
 
3582
                self._remote_path(), revision_id)
 
3583
        except errors.UnknownSmartMethod:
 
3584
            self._ensure_real()
 
3585
            return self._real_branch.revision_id_to_revno(revision_id)
 
3586
        if response[0] == 'ok':
 
3587
            if len(response) == 2:
 
3588
                return int(response[1])
 
3589
            raise NoSuchRevision(self, revision_id)
 
3590
        else:
 
3591
            raise errors.UnexpectedSmartServerResponse(response)
 
3592
 
 
3593
    @needs_write_lock
 
3594
    def set_last_revision_info(self, revno, revision_id):
 
3595
        # XXX: These should be returned by the set_last_revision_info verb
 
3596
        old_revno, old_revid = self.last_revision_info()
 
3597
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
3598
        if not revision_id or not isinstance(revision_id, basestring):
 
3599
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
3600
        try:
 
3601
            response = self._call('Branch.set_last_revision_info',
 
3602
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
3603
                str(revno), revision_id)
 
3604
        except errors.UnknownSmartMethod:
 
3605
            self._ensure_real()
 
3606
            self._clear_cached_state_of_remote_branch_only()
 
3607
            self._real_branch.set_last_revision_info(revno, revision_id)
 
3608
            self._last_revision_info_cache = revno, revision_id
 
3609
            return
 
3610
        if response == ('ok',):
 
3611
            self._clear_cached_state()
 
3612
            self._last_revision_info_cache = revno, revision_id
 
3613
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
3614
            # Update the _real_branch's cache too.
 
3615
            if self._real_branch is not None:
 
3616
                cache = self._last_revision_info_cache
 
3617
                self._real_branch._last_revision_info_cache = cache
 
3618
        else:
 
3619
            raise errors.UnexpectedSmartServerResponse(response)
 
3620
 
 
3621
    @needs_write_lock
 
3622
    def generate_revision_history(self, revision_id, last_rev=None,
 
3623
                                  other_branch=None):
 
3624
        medium = self._client._medium
 
3625
        if not medium._is_remote_before((1, 6)):
 
3626
            # Use a smart method for 1.6 and above servers
 
3627
            try:
 
3628
                self._set_last_revision_descendant(revision_id, other_branch,
 
3629
                    allow_diverged=True, allow_overwrite_descendant=True)
 
3630
                return
 
3631
            except errors.UnknownSmartMethod:
 
3632
                medium._remember_remote_is_before((1, 6))
 
3633
        self._clear_cached_state_of_remote_branch_only()
 
3634
        self._set_revision_history(self._lefthand_history(revision_id,
 
3635
            last_rev=last_rev,other_branch=other_branch))
 
3636
 
 
3637
    def set_push_location(self, location):
 
3638
        self._ensure_real()
 
3639
        return self._real_branch.set_push_location(location)
 
3640
 
 
3641
    def heads_to_fetch(self):
 
3642
        if self._format._use_default_local_heads_to_fetch():
 
3643
            # We recognise this format, and its heads-to-fetch implementation
 
3644
            # is the default one (tip + tags).  In this case it's cheaper to
 
3645
            # just use the default implementation rather than a special RPC as
 
3646
            # the tip and tags data is cached.
 
3647
            return branch.Branch.heads_to_fetch(self)
 
3648
        medium = self._client._medium
 
3649
        if medium._is_remote_before((2, 4)):
 
3650
            return self._vfs_heads_to_fetch()
 
3651
        try:
 
3652
            return self._rpc_heads_to_fetch()
 
3653
        except errors.UnknownSmartMethod:
 
3654
            medium._remember_remote_is_before((2, 4))
 
3655
            return self._vfs_heads_to_fetch()
 
3656
 
 
3657
    def _rpc_heads_to_fetch(self):
 
3658
        response = self._call('Branch.heads_to_fetch', self._remote_path())
 
3659
        if len(response) != 2:
 
3660
            raise errors.UnexpectedSmartServerResponse(response)
 
3661
        must_fetch, if_present_fetch = response
 
3662
        return set(must_fetch), set(if_present_fetch)
 
3663
 
 
3664
    def _vfs_heads_to_fetch(self):
 
3665
        self._ensure_real()
 
3666
        return self._real_branch.heads_to_fetch()
 
3667
 
 
3668
 
 
3669
class RemoteConfig(object):
 
3670
    """A Config that reads and writes from smart verbs.
 
3671
 
 
3672
    It is a low-level object that considers config data to be name/value pairs
 
3673
    that may be associated with a section. Assigning meaning to the these
 
3674
    values is done at higher levels like bzrlib.config.TreeConfig.
 
3675
    """
 
3676
 
 
3677
    def get_option(self, name, section=None, default=None):
 
3678
        """Return the value associated with a named option.
 
3679
 
 
3680
        :param name: The name of the value
 
3681
        :param section: The section the option is in (if any)
 
3682
        :param default: The value to return if the value is not set
 
3683
        :return: The value or default value
 
3684
        """
 
3685
        try:
 
3686
            configobj = self._get_configobj()
 
3687
            section_obj = None
 
3688
            if section is None:
 
3689
                section_obj = configobj
 
3690
            else:
 
3691
                try:
 
3692
                    section_obj = configobj[section]
 
3693
                except KeyError:
 
3694
                    pass
 
3695
            if section_obj is None:
 
3696
                value = default
 
3697
            else:
 
3698
                value = section_obj.get(name, default)
 
3699
        except errors.UnknownSmartMethod:
 
3700
            value = self._vfs_get_option(name, section, default)
 
3701
        for hook in config.OldConfigHooks['get']:
 
3702
            hook(self, name, value)
 
3703
        return value
 
3704
 
 
3705
    def _response_to_configobj(self, response):
 
3706
        if len(response[0]) and response[0][0] != 'ok':
 
3707
            raise errors.UnexpectedSmartServerResponse(response)
 
3708
        lines = response[1].read_body_bytes().splitlines()
 
3709
        conf = config.ConfigObj(lines, encoding='utf-8')
 
3710
        for hook in config.OldConfigHooks['load']:
 
3711
            hook(self)
 
3712
        return conf
 
3713
 
 
3714
 
 
3715
class RemoteBranchConfig(RemoteConfig):
 
3716
    """A RemoteConfig for Branches."""
 
3717
 
 
3718
    def __init__(self, branch):
 
3719
        self._branch = branch
 
3720
 
 
3721
    def _get_configobj(self):
 
3722
        path = self._branch._remote_path()
 
3723
        response = self._branch._client.call_expecting_body(
 
3724
            'Branch.get_config_file', path)
 
3725
        return self._response_to_configobj(response)
 
3726
 
 
3727
    def set_option(self, value, name, section=None):
 
3728
        """Set the value associated with a named option.
 
3729
 
 
3730
        :param value: The value to set
 
3731
        :param name: The name of the value to set
 
3732
        :param section: The section the option is in (if any)
 
3733
        """
 
3734
        medium = self._branch._client._medium
 
3735
        if medium._is_remote_before((1, 14)):
 
3736
            return self._vfs_set_option(value, name, section)
 
3737
        if isinstance(value, dict):
 
3738
            if medium._is_remote_before((2, 2)):
 
3739
                return self._vfs_set_option(value, name, section)
 
3740
            return self._set_config_option_dict(value, name, section)
 
3741
        else:
 
3742
            return self._set_config_option(value, name, section)
 
3743
 
 
3744
    def _set_config_option(self, value, name, section):
 
3745
        try:
 
3746
            path = self._branch._remote_path()
 
3747
            response = self._branch._client.call('Branch.set_config_option',
 
3748
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
3749
                value.encode('utf8'), name, section or '')
 
3750
        except errors.UnknownSmartMethod:
 
3751
            medium = self._branch._client._medium
 
3752
            medium._remember_remote_is_before((1, 14))
 
3753
            return self._vfs_set_option(value, name, section)
 
3754
        if response != ():
 
3755
            raise errors.UnexpectedSmartServerResponse(response)
 
3756
 
 
3757
    def _serialize_option_dict(self, option_dict):
 
3758
        utf8_dict = {}
 
3759
        for key, value in option_dict.items():
 
3760
            if isinstance(key, unicode):
 
3761
                key = key.encode('utf8')
 
3762
            if isinstance(value, unicode):
 
3763
                value = value.encode('utf8')
 
3764
            utf8_dict[key] = value
 
3765
        return bencode.bencode(utf8_dict)
 
3766
 
 
3767
    def _set_config_option_dict(self, value, name, section):
 
3768
        try:
 
3769
            path = self._branch._remote_path()
 
3770
            serialised_dict = self._serialize_option_dict(value)
 
3771
            response = self._branch._client.call(
 
3772
                'Branch.set_config_option_dict',
 
3773
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
3774
                serialised_dict, name, section or '')
 
3775
        except errors.UnknownSmartMethod:
 
3776
            medium = self._branch._client._medium
 
3777
            medium._remember_remote_is_before((2, 2))
 
3778
            return self._vfs_set_option(value, name, section)
 
3779
        if response != ():
 
3780
            raise errors.UnexpectedSmartServerResponse(response)
 
3781
 
 
3782
    def _real_object(self):
 
3783
        self._branch._ensure_real()
 
3784
        return self._branch._real_branch
 
3785
 
 
3786
    def _vfs_set_option(self, value, name, section=None):
 
3787
        return self._real_object()._get_config().set_option(
 
3788
            value, name, section)
 
3789
 
 
3790
 
 
3791
class RemoteBzrDirConfig(RemoteConfig):
 
3792
    """A RemoteConfig for BzrDirs."""
 
3793
 
 
3794
    def __init__(self, bzrdir):
 
3795
        self._bzrdir = bzrdir
 
3796
 
 
3797
    def _get_configobj(self):
 
3798
        medium = self._bzrdir._client._medium
 
3799
        verb = 'BzrDir.get_config_file'
 
3800
        if medium._is_remote_before((1, 15)):
 
3801
            raise errors.UnknownSmartMethod(verb)
 
3802
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
 
3803
        response = self._bzrdir._call_expecting_body(
 
3804
            verb, path)
 
3805
        return self._response_to_configobj(response)
 
3806
 
 
3807
    def _vfs_get_option(self, name, section, default):
 
3808
        return self._real_object()._get_config().get_option(
 
3809
            name, section, default)
 
3810
 
 
3811
    def set_option(self, value, name, section=None):
 
3812
        """Set the value associated with a named option.
 
3813
 
 
3814
        :param value: The value to set
 
3815
        :param name: The name of the value to set
 
3816
        :param section: The section the option is in (if any)
 
3817
        """
 
3818
        return self._real_object()._get_config().set_option(
 
3819
            value, name, section)
 
3820
 
 
3821
    def _real_object(self):
 
3822
        self._bzrdir._ensure_real()
 
3823
        return self._bzrdir._real_bzrdir
 
3824
 
 
3825
 
 
3826
def _extract_tar(tar, to_dir):
 
3827
    """Extract all the contents of a tarfile object.
 
3828
 
 
3829
    A replacement for extractall, which is not present in python2.4
 
3830
    """
 
3831
    for tarinfo in tar:
 
3832
        tar.extract(tarinfo, to_dir)
 
3833
 
 
3834
 
 
3835
error_translators = registry.Registry()
 
3836
no_context_error_translators = registry.Registry()
 
3837
 
 
3838
 
 
3839
def _translate_error(err, **context):
 
3840
    """Translate an ErrorFromSmartServer into a more useful error.
 
3841
 
 
3842
    Possible context keys:
 
3843
      - branch
 
3844
      - repository
 
3845
      - bzrdir
 
3846
      - token
 
3847
      - other_branch
 
3848
      - path
 
3849
 
 
3850
    If the error from the server doesn't match a known pattern, then
 
3851
    UnknownErrorFromSmartServer is raised.
 
3852
    """
 
3853
    def find(name):
 
3854
        try:
 
3855
            return context[name]
 
3856
        except KeyError, key_err:
 
3857
            mutter('Missing key %r in context %r', key_err.args[0], context)
 
3858
            raise err
 
3859
    def get_path():
 
3860
        """Get the path from the context if present, otherwise use first error
 
3861
        arg.
 
3862
        """
 
3863
        try:
 
3864
            return context['path']
 
3865
        except KeyError, key_err:
 
3866
            try:
 
3867
                return err.error_args[0]
 
3868
            except IndexError, idx_err:
 
3869
                mutter(
 
3870
                    'Missing key %r in context %r', key_err.args[0], context)
 
3871
                raise err
 
3872
 
 
3873
    try:
 
3874
        translator = error_translators.get(err.error_verb)
 
3875
    except KeyError:
 
3876
        pass
 
3877
    else:
 
3878
        raise translator(err, find, get_path)
 
3879
    try:
 
3880
        translator = no_context_error_translators.get(err.error_verb)
 
3881
    except KeyError:
 
3882
        raise errors.UnknownErrorFromSmartServer(err)
 
3883
    else:
 
3884
        raise translator(err)
 
3885
 
 
3886
 
 
3887
error_translators.register('NoSuchRevision',
 
3888
    lambda err, find, get_path: NoSuchRevision(
 
3889
        find('branch'), err.error_args[0]))
 
3890
error_translators.register('nosuchrevision',
 
3891
    lambda err, find, get_path: NoSuchRevision(
 
3892
        find('repository'), err.error_args[0]))
 
3893
 
 
3894
def _translate_nobranch_error(err, find, get_path):
 
3895
    if len(err.error_args) >= 1:
 
3896
        extra = err.error_args[0]
 
3897
    else:
 
3898
        extra = None
 
3899
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
3900
        detail=extra)
 
3901
 
 
3902
error_translators.register('nobranch', _translate_nobranch_error)
 
3903
error_translators.register('norepository',
 
3904
    lambda err, find, get_path: errors.NoRepositoryPresent(
 
3905
        find('bzrdir')))
 
3906
error_translators.register('UnlockableTransport',
 
3907
    lambda err, find, get_path: errors.UnlockableTransport(
 
3908
        find('bzrdir').root_transport))
 
3909
error_translators.register('TokenMismatch',
 
3910
    lambda err, find, get_path: errors.TokenMismatch(
 
3911
        find('token'), '(remote token)'))
 
3912
error_translators.register('Diverged',
 
3913
    lambda err, find, get_path: errors.DivergedBranches(
 
3914
        find('branch'), find('other_branch')))
 
3915
error_translators.register('NotStacked',
 
3916
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
 
3917
 
 
3918
def _translate_PermissionDenied(err, find, get_path):
 
3919
    path = get_path()
 
3920
    if len(err.error_args) >= 2:
 
3921
        extra = err.error_args[1]
 
3922
    else:
 
3923
        extra = None
 
3924
    return errors.PermissionDenied(path, extra=extra)
 
3925
 
 
3926
error_translators.register('PermissionDenied', _translate_PermissionDenied)
 
3927
error_translators.register('ReadError',
 
3928
    lambda err, find, get_path: errors.ReadError(get_path()))
 
3929
error_translators.register('NoSuchFile',
 
3930
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
 
3931
error_translators.register('UnsuspendableWriteGroup',
 
3932
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
 
3933
        repository=find('repository')))
 
3934
error_translators.register('UnresumableWriteGroup',
 
3935
    lambda err, find, get_path: errors.UnresumableWriteGroup(
 
3936
        repository=find('repository'), write_groups=err.error_args[0],
 
3937
        reason=err.error_args[1]))
 
3938
no_context_error_translators.register('IncompatibleRepositories',
 
3939
    lambda err: errors.IncompatibleRepositories(
 
3940
        err.error_args[0], err.error_args[1], err.error_args[2]))
 
3941
no_context_error_translators.register('LockContention',
 
3942
    lambda err: errors.LockContention('(remote lock)'))
 
3943
no_context_error_translators.register('LockFailed',
 
3944
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
 
3945
no_context_error_translators.register('TipChangeRejected',
 
3946
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
 
3947
no_context_error_translators.register('UnstackableBranchFormat',
 
3948
    lambda err: errors.UnstackableBranchFormat(*err.error_args))
 
3949
no_context_error_translators.register('UnstackableRepositoryFormat',
 
3950
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
 
3951
no_context_error_translators.register('FileExists',
 
3952
    lambda err: errors.FileExists(err.error_args[0]))
 
3953
no_context_error_translators.register('DirectoryNotEmpty',
 
3954
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
 
3955
 
 
3956
def _translate_short_readv_error(err):
 
3957
    args = err.error_args
 
3958
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
 
3959
        int(args[3]))
 
3960
 
 
3961
no_context_error_translators.register('ShortReadvError',
 
3962
    _translate_short_readv_error)
 
3963
 
 
3964
def _translate_unicode_error(err):
 
3965
        encoding = str(err.error_args[0]) # encoding must always be a string
 
3966
        val = err.error_args[1]
 
3967
        start = int(err.error_args[2])
 
3968
        end = int(err.error_args[3])
 
3969
        reason = str(err.error_args[4]) # reason must always be a string
 
3970
        if val.startswith('u:'):
 
3971
            val = val[2:].decode('utf-8')
 
3972
        elif val.startswith('s:'):
 
3973
            val = val[2:].decode('base64')
 
3974
        if err.error_verb == 'UnicodeDecodeError':
 
3975
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
3976
        elif err.error_verb == 'UnicodeEncodeError':
 
3977
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
3978
 
 
3979
no_context_error_translators.register('UnicodeEncodeError',
 
3980
    _translate_unicode_error)
 
3981
no_context_error_translators.register('UnicodeDecodeError',
 
3982
    _translate_unicode_error)
 
3983
no_context_error_translators.register('ReadOnlyError',
 
3984
    lambda err: errors.TransportNotPossible('readonly transport'))
 
3985
no_context_error_translators.register('MemoryError',
 
3986
    lambda err: errors.BzrError("remote server out of memory\n"
 
3987
        "Retry non-remotely, or contact the server admin for details."))
 
3988
no_context_error_translators.register('RevisionNotPresent',
 
3989
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
 
3990
 
 
3991
no_context_error_translators.register('BzrCheckError',
 
3992
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
 
3993