~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-28 04:16:10 UTC
  • Revision ID: mbp@sourcefrog.net-20050328041610-0b9dfa40f77c7671
fix up debug output for check command

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