/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Ian Clatworthy
  • Date: 2009-02-26 11:28:50 UTC
  • mto: (4084.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 4085.
  • Revision ID: ian.clatworthy@canonical.com-20090226112850-e4pdzx2nafu3jso3
added Organizing your workspace to the User Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
# TODO: At some point, handle upgrades by just passing the whole request
 
18
# across to run on the server.
 
19
 
 
20
import bz2
 
21
 
 
22
from bzrlib import (
 
23
    branch,
 
24
    bzrdir,
 
25
    debug,
 
26
    errors,
 
27
    graph,
 
28
    lockdir,
 
29
    pack,
 
30
    repository,
 
31
    revision,
 
32
    symbol_versioning,
 
33
    urlutils,
 
34
)
 
35
from bzrlib.branch import BranchReferenceFormat
 
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
38
from bzrlib.errors import (
 
39
    NoSuchRevision,
 
40
    SmartProtocolError,
 
41
    )
 
42
from bzrlib.lockable_files import LockableFiles
 
43
from bzrlib.smart import client, vfs
 
44
from bzrlib.revision import ensure_null, NULL_REVISION
 
45
from bzrlib.trace import mutter, note, warning
 
46
from bzrlib.util import bencode
 
47
from bzrlib.versionedfile import record_to_fulltext_bytes
 
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_expecting_body(self, method, args, body_bytes,
 
66
                                             **err_context):
 
67
        try:
 
68
            return self._client.call_with_body_bytes_expecting_body(
 
69
                method, args, body_bytes)
 
70
        except errors.ErrorFromSmartServer, err:
 
71
            self._translate_error(err, **err_context)
 
72
 
 
73
 
 
74
def response_tuple_to_repo_format(response):
 
75
    """Convert a response tuple describing a repository format to a format."""
 
76
    format = RemoteRepositoryFormat()
 
77
    format.rich_root_data = (response[0] == 'yes')
 
78
    format.supports_tree_reference = (response[1] == 'yes')
 
79
    format.supports_external_lookups = (response[2] == 'yes')
 
80
    format._network_name = response[3]
 
81
    return format
 
82
 
 
83
 
 
84
# Note: RemoteBzrDirFormat is in bzrdir.py
 
85
 
 
86
class RemoteBzrDir(BzrDir, _RpcHelper):
 
87
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
88
 
 
89
    def __init__(self, transport, format, _client=None):
 
90
        """Construct a RemoteBzrDir.
 
91
 
 
92
        :param _client: Private parameter for testing. Disables probing and the
 
93
            use of a real bzrdir.
 
94
        """
 
95
        BzrDir.__init__(self, transport, format)
 
96
        # this object holds a delegated bzrdir that uses file-level operations
 
97
        # to talk to the other side
 
98
        self._real_bzrdir = None
 
99
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
 
100
        # create_branch for details.
 
101
        self._next_open_branch_result = None
 
102
 
 
103
        if _client is None:
 
104
            medium = transport.get_smart_medium()
 
105
            self._client = client._SmartClient(medium)
 
106
        else:
 
107
            self._client = _client
 
108
            return
 
109
 
 
110
        path = self._path_for_remote_call(self._client)
 
111
        response = self._call('BzrDir.open', path)
 
112
        if response not in [('yes',), ('no',)]:
 
113
            raise errors.UnexpectedSmartServerResponse(response)
 
114
        if response == ('no',):
 
115
            raise errors.NotBranchError(path=transport.base)
 
116
 
 
117
    def _ensure_real(self):
 
118
        """Ensure that there is a _real_bzrdir set.
 
119
 
 
120
        Used before calls to self._real_bzrdir.
 
121
        """
 
122
        if not self._real_bzrdir:
 
123
            self._real_bzrdir = BzrDir.open_from_transport(
 
124
                self.root_transport, _server_formats=False)
 
125
 
 
126
    def _translate_error(self, err, **context):
 
127
        _translate_error(err, bzrdir=self, **context)
 
128
 
 
129
    def break_lock(self):
 
130
        # Prevent aliasing problems in the next_open_branch_result cache.
 
131
        # See create_branch for rationale.
 
132
        self._next_open_branch_result = None
 
133
        return BzrDir.break_lock(self)
 
134
 
 
135
    def cloning_metadir(self, stacked=False):
 
136
        self._ensure_real()
 
137
        return self._real_bzrdir.cloning_metadir(stacked)
 
138
 
 
139
    def create_repository(self, shared=False):
 
140
        # as per meta1 formats - just delegate to the format object which may
 
141
        # be parameterised.
 
142
        result = self._format.repository_format.initialize(self, shared)
 
143
        if not isinstance(result, RemoteRepository):
 
144
            return self.open_repository()
 
145
        else:
 
146
            return result
 
147
 
 
148
    def destroy_repository(self):
 
149
        """See BzrDir.destroy_repository"""
 
150
        self._ensure_real()
 
151
        self._real_bzrdir.destroy_repository()
 
152
 
 
153
    def create_branch(self):
 
154
        # as per meta1 formats - just delegate to the format object which may
 
155
        # be parameterised.
 
156
        real_branch = self._format.get_branch_format().initialize(self)
 
157
        if not isinstance(real_branch, RemoteBranch):
 
158
            result = RemoteBranch(self, self.find_repository(), real_branch)
 
159
        else:
 
160
            result = real_branch
 
161
        # BzrDir.clone_on_transport() uses the result of create_branch but does
 
162
        # not return it to its callers; we save approximately 8% of our round
 
163
        # trips by handing the branch we created back to the first caller to
 
164
        # open_branch rather than probing anew. Long term we need a API in
 
165
        # bzrdir that doesn't discard result objects (like result_branch).
 
166
        # RBC 20090225
 
167
        self._next_open_branch_result = result
 
168
        return result
 
169
 
 
170
    def destroy_branch(self):
 
171
        """See BzrDir.destroy_branch"""
 
172
        self._ensure_real()
 
173
        self._real_bzrdir.destroy_branch()
 
174
        self._next_open_branch_result = None
 
175
 
 
176
    def create_workingtree(self, revision_id=None, from_branch=None):
 
177
        raise errors.NotLocalUrl(self.transport.base)
 
178
 
 
179
    def find_branch_format(self):
 
180
        """Find the branch 'format' for this bzrdir.
 
181
 
 
182
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
183
        """
 
184
        b = self.open_branch()
 
185
        return b._format
 
186
 
 
187
    def get_branch_reference(self):
 
188
        """See BzrDir.get_branch_reference()."""
 
189
        path = self._path_for_remote_call(self._client)
 
190
        response = self._call('BzrDir.open_branch', path)
 
191
        if response[0] == 'ok':
 
192
            if response[1] == '':
 
193
                # branch at this location.
 
194
                return None
 
195
            else:
 
196
                # a branch reference, use the existing BranchReference logic.
 
197
                return response[1]
 
198
        else:
 
199
            raise errors.UnexpectedSmartServerResponse(response)
 
200
 
 
201
    def _get_tree_branch(self):
 
202
        """See BzrDir._get_tree_branch()."""
 
203
        return None, self.open_branch()
 
204
 
 
205
    def open_branch(self, _unsupported=False):
 
206
        if _unsupported:
 
207
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
208
        if self._next_open_branch_result is not None:
 
209
            # See create_branch for details.
 
210
            result = self._next_open_branch_result
 
211
            self._next_open_branch_result = None
 
212
            return result
 
213
        reference_url = self.get_branch_reference()
 
214
        if reference_url is None:
 
215
            # branch at this location.
 
216
            return RemoteBranch(self, self.find_repository())
 
217
        else:
 
218
            # a branch reference, use the existing BranchReference logic.
 
219
            format = BranchReferenceFormat()
 
220
            return format.open(self, _found=True, location=reference_url)
 
221
 
 
222
    def open_repository(self):
 
223
        path = self._path_for_remote_call(self._client)
 
224
        verb = 'BzrDir.find_repositoryV2'
 
225
        try:
 
226
            response = self._call(verb, path)
 
227
        except errors.UnknownSmartMethod:
 
228
            verb = 'BzrDir.find_repository'
 
229
            response = self._call(verb, path)
 
230
        if response[0] != 'ok':
 
231
            raise errors.UnexpectedSmartServerResponse(response)
 
232
        if verb == 'BzrDir.find_repository':
 
233
            # servers that don't support the V2 method don't support external
 
234
            # references either.
 
235
            response = response + ('no', )
 
236
        if not (len(response) == 5):
 
237
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
238
        if response[1] == '':
 
239
            format = RemoteRepositoryFormat()
 
240
            format.rich_root_data = (response[2] == 'yes')
 
241
            format.supports_tree_reference = (response[3] == 'yes')
 
242
            # No wire format to check this yet.
 
243
            format.supports_external_lookups = (response[4] == 'yes')
 
244
            # Used to support creating a real format instance when needed.
 
245
            format._creating_bzrdir = self
 
246
            remote_repo = RemoteRepository(self, format)
 
247
            format._creating_repo = remote_repo
 
248
            return remote_repo
 
249
        else:
 
250
            raise errors.NoRepositoryPresent(self)
 
251
 
 
252
    def open_workingtree(self, recommend_upgrade=True):
 
253
        self._ensure_real()
 
254
        if self._real_bzrdir.has_workingtree():
 
255
            raise errors.NotLocalUrl(self.root_transport)
 
256
        else:
 
257
            raise errors.NoWorkingTree(self.root_transport.base)
 
258
 
 
259
    def _path_for_remote_call(self, client):
 
260
        """Return the path to be used for this bzrdir in a remote call."""
 
261
        return client.remote_path_from_transport(self.root_transport)
 
262
 
 
263
    def get_branch_transport(self, branch_format):
 
264
        self._ensure_real()
 
265
        return self._real_bzrdir.get_branch_transport(branch_format)
 
266
 
 
267
    def get_repository_transport(self, repository_format):
 
268
        self._ensure_real()
 
269
        return self._real_bzrdir.get_repository_transport(repository_format)
 
270
 
 
271
    def get_workingtree_transport(self, workingtree_format):
 
272
        self._ensure_real()
 
273
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
274
 
 
275
    def can_convert_format(self):
 
276
        """Upgrading of remote bzrdirs is not supported yet."""
 
277
        return False
 
278
 
 
279
    def needs_format_conversion(self, format=None):
 
280
        """Upgrading of remote bzrdirs is not supported yet."""
 
281
        if format is None:
 
282
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
283
                % 'needs_format_conversion(format=None)')
 
284
        return False
 
285
 
 
286
    def clone(self, url, revision_id=None, force_new_repo=False,
 
287
              preserve_stacking=False):
 
288
        self._ensure_real()
 
289
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
290
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
291
 
 
292
    def get_config(self):
 
293
        self._ensure_real()
 
294
        return self._real_bzrdir.get_config()
 
295
 
 
296
 
 
297
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
298
    """Format for repositories accessed over a _SmartClient.
 
299
 
 
300
    Instances of this repository are represented by RemoteRepository
 
301
    instances.
 
302
 
 
303
    The RemoteRepositoryFormat is parameterized during construction
 
304
    to reflect the capabilities of the real, remote format. Specifically
 
305
    the attributes rich_root_data and supports_tree_reference are set
 
306
    on a per instance basis, and are not set (and should not be) at
 
307
    the class level.
 
308
 
 
309
    :ivar _custom_format: If set, a specific concrete repository format that
 
310
        will be used when initializing a repository with this
 
311
        RemoteRepositoryFormat.
 
312
    :ivar _creating_repo: If set, the repository object that this
 
313
        RemoteRepositoryFormat was created for: it can be called into
 
314
        to obtain data like the network name.
 
315
    """
 
316
 
 
317
    _matchingbzrdir = RemoteBzrDirFormat()
 
318
 
 
319
    def __init__(self):
 
320
        repository.RepositoryFormat.__init__(self)
 
321
        self._custom_format = None
 
322
        self._network_name = None
 
323
        self._creating_bzrdir = None
 
324
 
 
325
    def _vfs_initialize(self, a_bzrdir, shared):
 
326
        """Helper for common code in initialize."""
 
327
        if self._custom_format:
 
328
            # Custom format requested
 
329
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
330
        elif self._creating_bzrdir is not None:
 
331
            # Use the format that the repository we were created to back
 
332
            # has.
 
333
            prior_repo = self._creating_bzrdir.open_repository()
 
334
            prior_repo._ensure_real()
 
335
            result = prior_repo._real_repository._format.initialize(
 
336
                a_bzrdir, shared=shared)
 
337
        else:
 
338
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
 
339
            # support remote initialization.
 
340
            # We delegate to a real object at this point (as RemoteBzrDir
 
341
            # delegate to the repository format which would lead to infinite
 
342
            # recursion if we just called a_bzrdir.create_repository.
 
343
            a_bzrdir._ensure_real()
 
344
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
345
        if not isinstance(result, RemoteRepository):
 
346
            return self.open(a_bzrdir)
 
347
        else:
 
348
            return result
 
349
 
 
350
    def initialize(self, a_bzrdir, shared=False):
 
351
        # Being asked to create on a non RemoteBzrDir:
 
352
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
353
            return self._vfs_initialize(a_bzrdir, shared)
 
354
        medium = a_bzrdir._client._medium
 
355
        if medium._is_remote_before((1, 13)):
 
356
            return self._vfs_initialize(a_bzrdir, shared)
 
357
        # Creating on a remote bzr dir.
 
358
        # 1) get the network name to use.
 
359
        if self._custom_format:
 
360
            network_name = self._custom_format.network_name()
 
361
        else:
 
362
            # Select the current bzrlib default and ask for that.
 
363
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
364
            reference_format = reference_bzrdir_format.repository_format
 
365
            network_name = reference_format.network_name()
 
366
        # 2) try direct creation via RPC
 
367
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
368
        verb = 'BzrDir.create_repository'
 
369
        if shared:
 
370
            shared_str = 'True'
 
371
        else:
 
372
            shared_str = 'False'
 
373
        try:
 
374
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
375
        except errors.UnknownSmartMethod:
 
376
            # Fallback - use vfs methods
 
377
            return self._vfs_initialize(a_bzrdir, shared)
 
378
        else:
 
379
            # Turn the response into a RemoteRepository object.
 
380
            format = response_tuple_to_repo_format(response[1:])
 
381
            # Used to support creating a real format instance when needed.
 
382
            format._creating_bzrdir = a_bzrdir
 
383
            remote_repo = RemoteRepository(a_bzrdir, format)
 
384
            format._creating_repo = remote_repo
 
385
            return remote_repo
 
386
 
 
387
    def open(self, a_bzrdir):
 
388
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
389
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
390
        return a_bzrdir.open_repository()
 
391
 
 
392
    def get_format_description(self):
 
393
        return 'bzr remote repository'
 
394
 
 
395
    def __eq__(self, other):
 
396
        return self.__class__ == other.__class__
 
397
 
 
398
    def check_conversion_target(self, target_format):
 
399
        if self.rich_root_data and not target_format.rich_root_data:
 
400
            raise errors.BadConversionTarget(
 
401
                'Does not support rich root data.', target_format)
 
402
        if (self.supports_tree_reference and
 
403
            not getattr(target_format, 'supports_tree_reference', False)):
 
404
            raise errors.BadConversionTarget(
 
405
                'Does not support nested trees', target_format)
 
406
 
 
407
    def network_name(self):
 
408
        if self._network_name:
 
409
            return self._network_name
 
410
        self._creating_repo._ensure_real()
 
411
        return self._creating_repo._real_repository._format.network_name()
 
412
 
 
413
    @property
 
414
    def _serializer(self):
 
415
        if self._custom_format is not None:
 
416
            return self._custom_format._serializer
 
417
        elif self._network_name is not None:
 
418
            self._custom_format = repository.network_format_registry.get(
 
419
                self._network_name)
 
420
            return self._custom_format._serializer
 
421
        else:
 
422
            # We should only be getting asked for the serializer for
 
423
            # RemoteRepositoryFormat objects when the RemoteRepositoryFormat object
 
424
            # is a concrete instance for a RemoteRepository. In this case we know
 
425
            # the creating_repo and can use it to supply the serializer.
 
426
            self._creating_repo._ensure_real()
 
427
            return self._creating_repo._real_repository._format._serializer
 
428
 
 
429
 
 
430
class RemoteRepository(_RpcHelper):
 
431
    """Repository accessed over rpc.
 
432
 
 
433
    For the moment most operations are performed using local transport-backed
 
434
    Repository objects.
 
435
    """
 
436
 
 
437
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
438
        """Create a RemoteRepository instance.
 
439
 
 
440
        :param remote_bzrdir: The bzrdir hosting this repository.
 
441
        :param format: The RemoteFormat object to use.
 
442
        :param real_repository: If not None, a local implementation of the
 
443
            repository logic for the repository, usually accessing the data
 
444
            via the VFS.
 
445
        :param _client: Private testing parameter - override the smart client
 
446
            to be used by the repository.
 
447
        """
 
448
        if real_repository:
 
449
            self._real_repository = real_repository
 
450
        else:
 
451
            self._real_repository = None
 
452
        self.bzrdir = remote_bzrdir
 
453
        if _client is None:
 
454
            self._client = remote_bzrdir._client
 
455
        else:
 
456
            self._client = _client
 
457
        self._format = format
 
458
        self._lock_mode = None
 
459
        self._lock_token = None
 
460
        self._lock_count = 0
 
461
        self._leave_lock = False
 
462
        self._unstacked_provider = graph.CachingParentsProvider(
 
463
            get_parent_map=self._get_parent_map_rpc)
 
464
        self._unstacked_provider.disable_cache()
 
465
        # For tests:
 
466
        # These depend on the actual remote format, so force them off for
 
467
        # maximum compatibility. XXX: In future these should depend on the
 
468
        # remote repository instance, but this is irrelevant until we perform
 
469
        # reconcile via an RPC call.
 
470
        self._reconcile_does_inventory_gc = False
 
471
        self._reconcile_fixes_text_parents = False
 
472
        self._reconcile_backsup_inventory = False
 
473
        self.base = self.bzrdir.transport.base
 
474
        # Additional places to query for data.
 
475
        self._fallback_repositories = []
 
476
 
 
477
    def __str__(self):
 
478
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
479
 
 
480
    __repr__ = __str__
 
481
 
 
482
    def abort_write_group(self, suppress_errors=False):
 
483
        """Complete a write group on the decorated repository.
 
484
 
 
485
        Smart methods peform operations in a single step so this api
 
486
        is not really applicable except as a compatibility thunk
 
487
        for older plugins that don't use e.g. the CommitBuilder
 
488
        facility.
 
489
 
 
490
        :param suppress_errors: see Repository.abort_write_group.
 
491
        """
 
492
        self._ensure_real()
 
493
        return self._real_repository.abort_write_group(
 
494
            suppress_errors=suppress_errors)
 
495
 
 
496
    def commit_write_group(self):
 
497
        """Complete a write group on the decorated repository.
 
498
 
 
499
        Smart methods peform operations in a single step so this api
 
500
        is not really applicable except as a compatibility thunk
 
501
        for older plugins that don't use e.g. the CommitBuilder
 
502
        facility.
 
503
        """
 
504
        self._ensure_real()
 
505
        return self._real_repository.commit_write_group()
 
506
 
 
507
    def resume_write_group(self, tokens):
 
508
        self._ensure_real()
 
509
        return self._real_repository.resume_write_group(tokens)
 
510
 
 
511
    def suspend_write_group(self):
 
512
        self._ensure_real()
 
513
        return self._real_repository.suspend_write_group()
 
514
 
 
515
    def _ensure_real(self):
 
516
        """Ensure that there is a _real_repository set.
 
517
 
 
518
        Used before calls to self._real_repository.
 
519
        """
 
520
        if self._real_repository is None:
 
521
            self.bzrdir._ensure_real()
 
522
            self._set_real_repository(
 
523
                self.bzrdir._real_bzrdir.open_repository())
 
524
 
 
525
    def _translate_error(self, err, **context):
 
526
        self.bzrdir._translate_error(err, repository=self, **context)
 
527
 
 
528
    def find_text_key_references(self):
 
529
        """Find the text key references within the repository.
 
530
 
 
531
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
532
        revision_ids. Each altered file-ids has the exact revision_ids that
 
533
        altered it listed explicitly.
 
534
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
535
            to whether they were referred to by the inventory of the
 
536
            revision_id that they contain. The inventory texts from all present
 
537
            revision ids are assessed to generate this report.
 
538
        """
 
539
        self._ensure_real()
 
540
        return self._real_repository.find_text_key_references()
 
541
 
 
542
    def _generate_text_key_index(self):
 
543
        """Generate a new text key index for the repository.
 
544
 
 
545
        This is an expensive function that will take considerable time to run.
 
546
 
 
547
        :return: A dict mapping (file_id, revision_id) tuples to a list of
 
548
            parents, also (file_id, revision_id) tuples.
 
549
        """
 
550
        self._ensure_real()
 
551
        return self._real_repository._generate_text_key_index()
 
552
 
 
553
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
 
554
    def get_revision_graph(self, revision_id=None):
 
555
        """See Repository.get_revision_graph()."""
 
556
        return self._get_revision_graph(revision_id)
 
557
 
 
558
    def _get_revision_graph(self, revision_id):
 
559
        """Private method for using with old (< 1.2) servers to fallback."""
 
560
        if revision_id is None:
 
561
            revision_id = ''
 
562
        elif revision.is_null(revision_id):
 
563
            return {}
 
564
 
 
565
        path = self.bzrdir._path_for_remote_call(self._client)
 
566
        response = self._call_expecting_body(
 
567
            'Repository.get_revision_graph', path, revision_id)
 
568
        response_tuple, response_handler = response
 
569
        if response_tuple[0] != 'ok':
 
570
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
571
        coded = response_handler.read_body_bytes()
 
572
        if coded == '':
 
573
            # no revisions in this repository!
 
574
            return {}
 
575
        lines = coded.split('\n')
 
576
        revision_graph = {}
 
577
        for line in lines:
 
578
            d = tuple(line.split())
 
579
            revision_graph[d[0]] = d[1:]
 
580
 
 
581
        return revision_graph
 
582
 
 
583
    def _get_sink(self):
 
584
        """See Repository._get_sink()."""
 
585
        return RemoteStreamSink(self)
 
586
 
 
587
    def has_revision(self, revision_id):
 
588
        """See Repository.has_revision()."""
 
589
        if revision_id == NULL_REVISION:
 
590
            # The null revision is always present.
 
591
            return True
 
592
        path = self.bzrdir._path_for_remote_call(self._client)
 
593
        response = self._call('Repository.has_revision', path, revision_id)
 
594
        if response[0] not in ('yes', 'no'):
 
595
            raise errors.UnexpectedSmartServerResponse(response)
 
596
        if response[0] == 'yes':
 
597
            return True
 
598
        for fallback_repo in self._fallback_repositories:
 
599
            if fallback_repo.has_revision(revision_id):
 
600
                return True
 
601
        return False
 
602
 
 
603
    def has_revisions(self, revision_ids):
 
604
        """See Repository.has_revisions()."""
 
605
        # FIXME: This does many roundtrips, particularly when there are
 
606
        # fallback repositories.  -- mbp 20080905
 
607
        result = set()
 
608
        for revision_id in revision_ids:
 
609
            if self.has_revision(revision_id):
 
610
                result.add(revision_id)
 
611
        return result
 
612
 
 
613
    def has_same_location(self, other):
 
614
        return (self.__class__ == other.__class__ and
 
615
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
616
 
 
617
    def get_graph(self, other_repository=None):
 
618
        """Return the graph for this repository format"""
 
619
        parents_provider = self._make_parents_provider(other_repository)
 
620
        return graph.Graph(parents_provider)
 
621
 
 
622
    def gather_stats(self, revid=None, committers=None):
 
623
        """See Repository.gather_stats()."""
 
624
        path = self.bzrdir._path_for_remote_call(self._client)
 
625
        # revid can be None to indicate no revisions, not just NULL_REVISION
 
626
        if revid is None or revision.is_null(revid):
 
627
            fmt_revid = ''
 
628
        else:
 
629
            fmt_revid = revid
 
630
        if committers is None or not committers:
 
631
            fmt_committers = 'no'
 
632
        else:
 
633
            fmt_committers = 'yes'
 
634
        response_tuple, response_handler = self._call_expecting_body(
 
635
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
636
        if response_tuple[0] != 'ok':
 
637
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
638
 
 
639
        body = response_handler.read_body_bytes()
 
640
        result = {}
 
641
        for line in body.split('\n'):
 
642
            if not line:
 
643
                continue
 
644
            key, val_text = line.split(':')
 
645
            if key in ('revisions', 'size', 'committers'):
 
646
                result[key] = int(val_text)
 
647
            elif key in ('firstrev', 'latestrev'):
 
648
                values = val_text.split(' ')[1:]
 
649
                result[key] = (float(values[0]), long(values[1]))
 
650
 
 
651
        return result
 
652
 
 
653
    def find_branches(self, using=False):
 
654
        """See Repository.find_branches()."""
 
655
        # should be an API call to the server.
 
656
        self._ensure_real()
 
657
        return self._real_repository.find_branches(using=using)
 
658
 
 
659
    def get_physical_lock_status(self):
 
660
        """See Repository.get_physical_lock_status()."""
 
661
        # should be an API call to the server.
 
662
        self._ensure_real()
 
663
        return self._real_repository.get_physical_lock_status()
 
664
 
 
665
    def is_in_write_group(self):
 
666
        """Return True if there is an open write group.
 
667
 
 
668
        write groups are only applicable locally for the smart server..
 
669
        """
 
670
        if self._real_repository:
 
671
            return self._real_repository.is_in_write_group()
 
672
 
 
673
    def is_locked(self):
 
674
        return self._lock_count >= 1
 
675
 
 
676
    def is_shared(self):
 
677
        """See Repository.is_shared()."""
 
678
        path = self.bzrdir._path_for_remote_call(self._client)
 
679
        response = self._call('Repository.is_shared', path)
 
680
        if response[0] not in ('yes', 'no'):
 
681
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
682
        return response[0] == 'yes'
 
683
 
 
684
    def is_write_locked(self):
 
685
        return self._lock_mode == 'w'
 
686
 
 
687
    def lock_read(self):
 
688
        # wrong eventually - want a local lock cache context
 
689
        if not self._lock_mode:
 
690
            self._lock_mode = 'r'
 
691
            self._lock_count = 1
 
692
            self._unstacked_provider.enable_cache(cache_misses=False)
 
693
            if self._real_repository is not None:
 
694
                self._real_repository.lock_read()
 
695
        else:
 
696
            self._lock_count += 1
 
697
 
 
698
    def _remote_lock_write(self, token):
 
699
        path = self.bzrdir._path_for_remote_call(self._client)
 
700
        if token is None:
 
701
            token = ''
 
702
        err_context = {'token': token}
 
703
        response = self._call('Repository.lock_write', path, token,
 
704
                              **err_context)
 
705
        if response[0] == 'ok':
 
706
            ok, token = response
 
707
            return token
 
708
        else:
 
709
            raise errors.UnexpectedSmartServerResponse(response)
 
710
 
 
711
    def lock_write(self, token=None, _skip_rpc=False):
 
712
        if not self._lock_mode:
 
713
            if _skip_rpc:
 
714
                if self._lock_token is not None:
 
715
                    if token != self._lock_token:
 
716
                        raise errors.TokenMismatch(token, self._lock_token)
 
717
                self._lock_token = token
 
718
            else:
 
719
                self._lock_token = self._remote_lock_write(token)
 
720
            # if self._lock_token is None, then this is something like packs or
 
721
            # svn where we don't get to lock the repo, or a weave style repository
 
722
            # where we cannot lock it over the wire and attempts to do so will
 
723
            # fail.
 
724
            if self._real_repository is not None:
 
725
                self._real_repository.lock_write(token=self._lock_token)
 
726
            if token is not None:
 
727
                self._leave_lock = True
 
728
            else:
 
729
                self._leave_lock = False
 
730
            self._lock_mode = 'w'
 
731
            self._lock_count = 1
 
732
            self._unstacked_provider.enable_cache(cache_misses=False)
 
733
        elif self._lock_mode == 'r':
 
734
            raise errors.ReadOnlyError(self)
 
735
        else:
 
736
            self._lock_count += 1
 
737
        return self._lock_token or None
 
738
 
 
739
    def leave_lock_in_place(self):
 
740
        if not self._lock_token:
 
741
            raise NotImplementedError(self.leave_lock_in_place)
 
742
        self._leave_lock = True
 
743
 
 
744
    def dont_leave_lock_in_place(self):
 
745
        if not self._lock_token:
 
746
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
747
        self._leave_lock = False
 
748
 
 
749
    def _set_real_repository(self, repository):
 
750
        """Set the _real_repository for this repository.
 
751
 
 
752
        :param repository: The repository to fallback to for non-hpss
 
753
            implemented operations.
 
754
        """
 
755
        if self._real_repository is not None:
 
756
            raise AssertionError('_real_repository is already set')
 
757
        if isinstance(repository, RemoteRepository):
 
758
            raise AssertionError()
 
759
        self._real_repository = repository
 
760
        for fb in self._fallback_repositories:
 
761
            self._real_repository.add_fallback_repository(fb)
 
762
        if self._lock_mode == 'w':
 
763
            # if we are already locked, the real repository must be able to
 
764
            # acquire the lock with our token.
 
765
            self._real_repository.lock_write(self._lock_token)
 
766
        elif self._lock_mode == 'r':
 
767
            self._real_repository.lock_read()
 
768
 
 
769
    def start_write_group(self):
 
770
        """Start a write group on the decorated repository.
 
771
 
 
772
        Smart methods peform operations in a single step so this api
 
773
        is not really applicable except as a compatibility thunk
 
774
        for older plugins that don't use e.g. the CommitBuilder
 
775
        facility.
 
776
        """
 
777
        self._ensure_real()
 
778
        return self._real_repository.start_write_group()
 
779
 
 
780
    def _unlock(self, token):
 
781
        path = self.bzrdir._path_for_remote_call(self._client)
 
782
        if not token:
 
783
            # with no token the remote repository is not persistently locked.
 
784
            return
 
785
        err_context = {'token': token}
 
786
        response = self._call('Repository.unlock', path, token,
 
787
                              **err_context)
 
788
        if response == ('ok',):
 
789
            return
 
790
        else:
 
791
            raise errors.UnexpectedSmartServerResponse(response)
 
792
 
 
793
    def unlock(self):
 
794
        if not self._lock_count:
 
795
            raise errors.LockNotHeld(self)
 
796
        self._lock_count -= 1
 
797
        if self._lock_count > 0:
 
798
            return
 
799
        self._unstacked_provider.disable_cache()
 
800
        old_mode = self._lock_mode
 
801
        self._lock_mode = None
 
802
        try:
 
803
            # The real repository is responsible at present for raising an
 
804
            # exception if it's in an unfinished write group.  However, it
 
805
            # normally will *not* actually remove the lock from disk - that's
 
806
            # done by the server on receiving the Repository.unlock call.
 
807
            # This is just to let the _real_repository stay up to date.
 
808
            if self._real_repository is not None:
 
809
                self._real_repository.unlock()
 
810
        finally:
 
811
            # The rpc-level lock should be released even if there was a
 
812
            # problem releasing the vfs-based lock.
 
813
            if old_mode == 'w':
 
814
                # Only write-locked repositories need to make a remote method
 
815
                # call to perfom the unlock.
 
816
                old_token = self._lock_token
 
817
                self._lock_token = None
 
818
                if not self._leave_lock:
 
819
                    self._unlock(old_token)
 
820
 
 
821
    def break_lock(self):
 
822
        # should hand off to the network
 
823
        self._ensure_real()
 
824
        return self._real_repository.break_lock()
 
825
 
 
826
    def _get_tarball(self, compression):
 
827
        """Return a TemporaryFile containing a repository tarball.
 
828
 
 
829
        Returns None if the server does not support sending tarballs.
 
830
        """
 
831
        import tempfile
 
832
        path = self.bzrdir._path_for_remote_call(self._client)
 
833
        try:
 
834
            response, protocol = self._call_expecting_body(
 
835
                'Repository.tarball', path, compression)
 
836
        except errors.UnknownSmartMethod:
 
837
            protocol.cancel_read_body()
 
838
            return None
 
839
        if response[0] == 'ok':
 
840
            # Extract the tarball and return it
 
841
            t = tempfile.NamedTemporaryFile()
 
842
            # TODO: rpc layer should read directly into it...
 
843
            t.write(protocol.read_body_bytes())
 
844
            t.seek(0)
 
845
            return t
 
846
        raise errors.UnexpectedSmartServerResponse(response)
 
847
 
 
848
    def sprout(self, to_bzrdir, revision_id=None):
 
849
        # TODO: Option to control what format is created?
 
850
        self._ensure_real()
 
851
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
852
                                                             shared=False)
 
853
        dest_repo.fetch(self, revision_id=revision_id)
 
854
        return dest_repo
 
855
 
 
856
    ### These methods are just thin shims to the VFS object for now.
 
857
 
 
858
    def revision_tree(self, revision_id):
 
859
        self._ensure_real()
 
860
        return self._real_repository.revision_tree(revision_id)
 
861
 
 
862
    def get_serializer_format(self):
 
863
        self._ensure_real()
 
864
        return self._real_repository.get_serializer_format()
 
865
 
 
866
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
867
                           timezone=None, committer=None, revprops=None,
 
868
                           revision_id=None):
 
869
        # FIXME: It ought to be possible to call this without immediately
 
870
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
871
        self._ensure_real()
 
872
        real_repo = self._real_repository
 
873
        builder = real_repo.get_commit_builder(branch, parents,
 
874
                config, timestamp=timestamp, timezone=timezone,
 
875
                committer=committer, revprops=revprops, revision_id=revision_id)
 
876
        return builder
 
877
 
 
878
    def add_fallback_repository(self, repository):
 
879
        """Add a repository to use for looking up data not held locally.
 
880
 
 
881
        :param repository: A repository.
 
882
        """
 
883
        # XXX: At the moment the RemoteRepository will allow fallbacks
 
884
        # unconditionally - however, a _real_repository will usually exist,
 
885
        # and may raise an error if it's not accommodated by the underlying
 
886
        # format.  Eventually we should check when opening the repository
 
887
        # whether it's willing to allow them or not.
 
888
        #
 
889
        # We need to accumulate additional repositories here, to pass them in
 
890
        # on various RPC's.
 
891
        #
 
892
        self._fallback_repositories.append(repository)
 
893
        # If self._real_repository was parameterised already (e.g. because a
 
894
        # _real_branch had its get_stacked_on_url method called), then the
 
895
        # repository to be added may already be in the _real_repositories list.
 
896
        if self._real_repository is not None:
 
897
            if repository not in self._real_repository._fallback_repositories:
 
898
                self._real_repository.add_fallback_repository(repository)
 
899
        else:
 
900
            # They are also seen by the fallback repository.  If it doesn't
 
901
            # exist yet they'll be added then.  This implicitly copies them.
 
902
            self._ensure_real()
 
903
 
 
904
    def add_inventory(self, revid, inv, parents):
 
905
        self._ensure_real()
 
906
        return self._real_repository.add_inventory(revid, inv, parents)
 
907
 
 
908
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
 
909
                               parents):
 
910
        self._ensure_real()
 
911
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
 
912
            delta, new_revision_id, parents)
 
913
 
 
914
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
915
        self._ensure_real()
 
916
        return self._real_repository.add_revision(
 
917
            rev_id, rev, inv=inv, config=config)
 
918
 
 
919
    @needs_read_lock
 
920
    def get_inventory(self, revision_id):
 
921
        self._ensure_real()
 
922
        return self._real_repository.get_inventory(revision_id)
 
923
 
 
924
    def iter_inventories(self, revision_ids):
 
925
        self._ensure_real()
 
926
        return self._real_repository.iter_inventories(revision_ids)
 
927
 
 
928
    @needs_read_lock
 
929
    def get_revision(self, revision_id):
 
930
        self._ensure_real()
 
931
        return self._real_repository.get_revision(revision_id)
 
932
 
 
933
    def get_transaction(self):
 
934
        self._ensure_real()
 
935
        return self._real_repository.get_transaction()
 
936
 
 
937
    @needs_read_lock
 
938
    def clone(self, a_bzrdir, revision_id=None):
 
939
        self._ensure_real()
 
940
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
941
 
 
942
    def make_working_trees(self):
 
943
        """See Repository.make_working_trees"""
 
944
        self._ensure_real()
 
945
        return self._real_repository.make_working_trees()
 
946
 
 
947
    def revision_ids_to_search_result(self, result_set):
 
948
        """Convert a set of revision ids to a graph SearchResult."""
 
949
        result_parents = set()
 
950
        for parents in self.get_graph().get_parent_map(
 
951
            result_set).itervalues():
 
952
            result_parents.update(parents)
 
953
        included_keys = result_set.intersection(result_parents)
 
954
        start_keys = result_set.difference(included_keys)
 
955
        exclude_keys = result_parents.difference(result_set)
 
956
        result = graph.SearchResult(start_keys, exclude_keys,
 
957
            len(result_set), result_set)
 
958
        return result
 
959
 
 
960
    @needs_read_lock
 
961
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
962
        """Return the revision ids that other has that this does not.
 
963
 
 
964
        These are returned in topological order.
 
965
 
 
966
        revision_id: only return revision ids included by revision_id.
 
967
        """
 
968
        return repository.InterRepository.get(
 
969
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
970
 
 
971
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
 
972
        # Not delegated to _real_repository so that InterRepository.get has a
 
973
        # chance to find an InterRepository specialised for RemoteRepository.
 
974
        if self.has_same_location(source):
 
975
            # check that last_revision is in 'from' and then return a
 
976
            # no-operation.
 
977
            if (revision_id is not None and
 
978
                not revision.is_null(revision_id)):
 
979
                self.get_revision(revision_id)
 
980
            return 0, []
 
981
        inter = repository.InterRepository.get(source, self)
 
982
        try:
 
983
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
 
984
        except NotImplementedError:
 
985
            raise errors.IncompatibleRepositories(source, self)
 
986
 
 
987
    def create_bundle(self, target, base, fileobj, format=None):
 
988
        self._ensure_real()
 
989
        self._real_repository.create_bundle(target, base, fileobj, format)
 
990
 
 
991
    @needs_read_lock
 
992
    def get_ancestry(self, revision_id, topo_sorted=True):
 
993
        self._ensure_real()
 
994
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
995
 
 
996
    def fileids_altered_by_revision_ids(self, revision_ids):
 
997
        self._ensure_real()
 
998
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
999
 
 
1000
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
 
1001
        self._ensure_real()
 
1002
        return self._real_repository._get_versioned_file_checker(
 
1003
            revisions, revision_versions_cache)
 
1004
 
 
1005
    def iter_files_bytes(self, desired_files):
 
1006
        """See Repository.iter_file_bytes.
 
1007
        """
 
1008
        self._ensure_real()
 
1009
        return self._real_repository.iter_files_bytes(desired_files)
 
1010
 
 
1011
    @property
 
1012
    def _fetch_order(self):
 
1013
        """Decorate the real repository for now.
 
1014
 
 
1015
        In the long term getting this back from the remote repository as part
 
1016
        of open would be more efficient.
 
1017
        """
 
1018
        self._ensure_real()
 
1019
        return self._real_repository._fetch_order
 
1020
 
 
1021
    @property
 
1022
    def _fetch_uses_deltas(self):
 
1023
        """Decorate the real repository for now.
 
1024
 
 
1025
        In the long term getting this back from the remote repository as part
 
1026
        of open would be more efficient.
 
1027
        """
 
1028
        self._ensure_real()
 
1029
        return self._real_repository._fetch_uses_deltas
 
1030
 
 
1031
    @property
 
1032
    def _fetch_reconcile(self):
 
1033
        """Decorate the real repository for now.
 
1034
 
 
1035
        In the long term getting this back from the remote repository as part
 
1036
        of open would be more efficient.
 
1037
        """
 
1038
        self._ensure_real()
 
1039
        return self._real_repository._fetch_reconcile
 
1040
 
 
1041
    def get_parent_map(self, revision_ids):
 
1042
        """See bzrlib.Graph.get_parent_map()."""
 
1043
        return self._make_parents_provider().get_parent_map(revision_ids)
 
1044
 
 
1045
    def _get_parent_map_rpc(self, keys):
 
1046
        """Helper for get_parent_map that performs the RPC."""
 
1047
        medium = self._client._medium
 
1048
        if medium._is_remote_before((1, 2)):
 
1049
            # We already found out that the server can't understand
 
1050
            # Repository.get_parent_map requests, so just fetch the whole
 
1051
            # graph.
 
1052
            # XXX: Note that this will issue a deprecation warning. This is ok
 
1053
            # :- its because we're working with a deprecated server anyway, and
 
1054
            # the user will almost certainly have seen a warning about the
 
1055
            # server version already.
 
1056
            rg = self.get_revision_graph()
 
1057
            # There is an api discrepency between get_parent_map and
 
1058
            # get_revision_graph. Specifically, a "key:()" pair in
 
1059
            # get_revision_graph just means a node has no parents. For
 
1060
            # "get_parent_map" it means the node is a ghost. So fix up the
 
1061
            # graph to correct this.
 
1062
            #   https://bugs.launchpad.net/bzr/+bug/214894
 
1063
            # There is one other "bug" which is that ghosts in
 
1064
            # get_revision_graph() are not returned at all. But we won't worry
 
1065
            # about that for now.
 
1066
            for node_id, parent_ids in rg.iteritems():
 
1067
                if parent_ids == ():
 
1068
                    rg[node_id] = (NULL_REVISION,)
 
1069
            rg[NULL_REVISION] = ()
 
1070
            return rg
 
1071
 
 
1072
        keys = set(keys)
 
1073
        if None in keys:
 
1074
            raise ValueError('get_parent_map(None) is not valid')
 
1075
        if NULL_REVISION in keys:
 
1076
            keys.discard(NULL_REVISION)
 
1077
            found_parents = {NULL_REVISION:()}
 
1078
            if not keys:
 
1079
                return found_parents
 
1080
        else:
 
1081
            found_parents = {}
 
1082
        # TODO(Needs analysis): We could assume that the keys being requested
 
1083
        # from get_parent_map are in a breadth first search, so typically they
 
1084
        # will all be depth N from some common parent, and we don't have to
 
1085
        # have the server iterate from the root parent, but rather from the
 
1086
        # keys we're searching; and just tell the server the keyspace we
 
1087
        # already have; but this may be more traffic again.
 
1088
 
 
1089
        # Transform self._parents_map into a search request recipe.
 
1090
        # TODO: Manage this incrementally to avoid covering the same path
 
1091
        # repeatedly. (The server will have to on each request, but the less
 
1092
        # work done the better).
 
1093
        parents_map = self._unstacked_provider.get_cached_map()
 
1094
        if parents_map is None:
 
1095
            # Repository is not locked, so there's no cache.
 
1096
            parents_map = {}
 
1097
        start_set = set(parents_map)
 
1098
        result_parents = set()
 
1099
        for parents in parents_map.itervalues():
 
1100
            result_parents.update(parents)
 
1101
        stop_keys = result_parents.difference(start_set)
 
1102
        included_keys = start_set.intersection(result_parents)
 
1103
        start_set.difference_update(included_keys)
 
1104
        recipe = (start_set, stop_keys, len(parents_map))
 
1105
        body = self._serialise_search_recipe(recipe)
 
1106
        path = self.bzrdir._path_for_remote_call(self._client)
 
1107
        for key in keys:
 
1108
            if type(key) is not str:
 
1109
                raise ValueError(
 
1110
                    "key %r not a plain string" % (key,))
 
1111
        verb = 'Repository.get_parent_map'
 
1112
        args = (path,) + tuple(keys)
 
1113
        try:
 
1114
            response = self._call_with_body_bytes_expecting_body(
 
1115
                verb, args, body)
 
1116
        except errors.UnknownSmartMethod:
 
1117
            # Server does not support this method, so get the whole graph.
 
1118
            # Worse, we have to force a disconnection, because the server now
 
1119
            # doesn't realise it has a body on the wire to consume, so the
 
1120
            # only way to recover is to abandon the connection.
 
1121
            warning(
 
1122
                'Server is too old for fast get_parent_map, reconnecting.  '
 
1123
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
1124
            medium.disconnect()
 
1125
            # To avoid having to disconnect repeatedly, we keep track of the
 
1126
            # fact the server doesn't understand remote methods added in 1.2.
 
1127
            medium._remember_remote_is_before((1, 2))
 
1128
            return self.get_revision_graph(None)
 
1129
        response_tuple, response_handler = response
 
1130
        if response_tuple[0] not in ['ok']:
 
1131
            response_handler.cancel_read_body()
 
1132
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1133
        if response_tuple[0] == 'ok':
 
1134
            coded = bz2.decompress(response_handler.read_body_bytes())
 
1135
            if coded == '':
 
1136
                # no revisions found
 
1137
                return {}
 
1138
            lines = coded.split('\n')
 
1139
            revision_graph = {}
 
1140
            for line in lines:
 
1141
                d = tuple(line.split())
 
1142
                if len(d) > 1:
 
1143
                    revision_graph[d[0]] = d[1:]
 
1144
                else:
 
1145
                    # No parents - so give the Graph result (NULL_REVISION,).
 
1146
                    revision_graph[d[0]] = (NULL_REVISION,)
 
1147
            return revision_graph
 
1148
 
 
1149
    @needs_read_lock
 
1150
    def get_signature_text(self, revision_id):
 
1151
        self._ensure_real()
 
1152
        return self._real_repository.get_signature_text(revision_id)
 
1153
 
 
1154
    @needs_read_lock
 
1155
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
 
1156
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1157
        self._ensure_real()
 
1158
        return self._real_repository.get_revision_graph_with_ghosts(
 
1159
            revision_ids=revision_ids)
 
1160
 
 
1161
    @needs_read_lock
 
1162
    def get_inventory_xml(self, revision_id):
 
1163
        self._ensure_real()
 
1164
        return self._real_repository.get_inventory_xml(revision_id)
 
1165
 
 
1166
    def deserialise_inventory(self, revision_id, xml):
 
1167
        self._ensure_real()
 
1168
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
1169
 
 
1170
    def reconcile(self, other=None, thorough=False):
 
1171
        self._ensure_real()
 
1172
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
1173
 
 
1174
    def all_revision_ids(self):
 
1175
        self._ensure_real()
 
1176
        return self._real_repository.all_revision_ids()
 
1177
 
 
1178
    @needs_read_lock
 
1179
    def get_deltas_for_revisions(self, revisions):
 
1180
        self._ensure_real()
 
1181
        return self._real_repository.get_deltas_for_revisions(revisions)
 
1182
 
 
1183
    @needs_read_lock
 
1184
    def get_revision_delta(self, revision_id):
 
1185
        self._ensure_real()
 
1186
        return self._real_repository.get_revision_delta(revision_id)
 
1187
 
 
1188
    @needs_read_lock
 
1189
    def revision_trees(self, revision_ids):
 
1190
        self._ensure_real()
 
1191
        return self._real_repository.revision_trees(revision_ids)
 
1192
 
 
1193
    @needs_read_lock
 
1194
    def get_revision_reconcile(self, revision_id):
 
1195
        self._ensure_real()
 
1196
        return self._real_repository.get_revision_reconcile(revision_id)
 
1197
 
 
1198
    @needs_read_lock
 
1199
    def check(self, revision_ids=None):
 
1200
        self._ensure_real()
 
1201
        return self._real_repository.check(revision_ids=revision_ids)
 
1202
 
 
1203
    def copy_content_into(self, destination, revision_id=None):
 
1204
        self._ensure_real()
 
1205
        return self._real_repository.copy_content_into(
 
1206
            destination, revision_id=revision_id)
 
1207
 
 
1208
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
1209
        # get a tarball of the remote repository, and copy from that into the
 
1210
        # destination
 
1211
        from bzrlib import osutils
 
1212
        import tarfile
 
1213
        # TODO: Maybe a progress bar while streaming the tarball?
 
1214
        note("Copying repository content as tarball...")
 
1215
        tar_file = self._get_tarball('bz2')
 
1216
        if tar_file is None:
 
1217
            return None
 
1218
        destination = to_bzrdir.create_repository()
 
1219
        try:
 
1220
            tar = tarfile.open('repository', fileobj=tar_file,
 
1221
                mode='r|bz2')
 
1222
            tmpdir = osutils.mkdtemp()
 
1223
            try:
 
1224
                _extract_tar(tar, tmpdir)
 
1225
                tmp_bzrdir = BzrDir.open(tmpdir)
 
1226
                tmp_repo = tmp_bzrdir.open_repository()
 
1227
                tmp_repo.copy_content_into(destination, revision_id)
 
1228
            finally:
 
1229
                osutils.rmtree(tmpdir)
 
1230
        finally:
 
1231
            tar_file.close()
 
1232
        return destination
 
1233
        # TODO: Suggestion from john: using external tar is much faster than
 
1234
        # python's tarfile library, but it may not work on windows.
 
1235
 
 
1236
    @property
 
1237
    def inventories(self):
 
1238
        """Decorate the real repository for now.
 
1239
 
 
1240
        In the long term a full blown network facility is needed to
 
1241
        avoid creating a real repository object locally.
 
1242
        """
 
1243
        self._ensure_real()
 
1244
        return self._real_repository.inventories
 
1245
 
 
1246
    @needs_write_lock
 
1247
    def pack(self):
 
1248
        """Compress the data within the repository.
 
1249
 
 
1250
        This is not currently implemented within the smart server.
 
1251
        """
 
1252
        self._ensure_real()
 
1253
        return self._real_repository.pack()
 
1254
 
 
1255
    @property
 
1256
    def revisions(self):
 
1257
        """Decorate the real repository for now.
 
1258
 
 
1259
        In the short term this should become a real object to intercept graph
 
1260
        lookups.
 
1261
 
 
1262
        In the long term a full blown network facility is needed.
 
1263
        """
 
1264
        self._ensure_real()
 
1265
        return self._real_repository.revisions
 
1266
 
 
1267
    def set_make_working_trees(self, new_value):
 
1268
        if new_value:
 
1269
            new_value_str = "True"
 
1270
        else:
 
1271
            new_value_str = "False"
 
1272
        path = self.bzrdir._path_for_remote_call(self._client)
 
1273
        try:
 
1274
            response = self._call(
 
1275
                'Repository.set_make_working_trees', path, new_value_str)
 
1276
        except errors.UnknownSmartMethod:
 
1277
            self._ensure_real()
 
1278
            self._real_repository.set_make_working_trees(new_value)
 
1279
        else:
 
1280
            if response[0] != 'ok':
 
1281
                raise errors.UnexpectedSmartServerResponse(response)
 
1282
 
 
1283
    @property
 
1284
    def signatures(self):
 
1285
        """Decorate the real repository for now.
 
1286
 
 
1287
        In the long term a full blown network facility is needed to avoid
 
1288
        creating a real repository object locally.
 
1289
        """
 
1290
        self._ensure_real()
 
1291
        return self._real_repository.signatures
 
1292
 
 
1293
    @needs_write_lock
 
1294
    def sign_revision(self, revision_id, gpg_strategy):
 
1295
        self._ensure_real()
 
1296
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
1297
 
 
1298
    @property
 
1299
    def texts(self):
 
1300
        """Decorate the real repository for now.
 
1301
 
 
1302
        In the long term a full blown network facility is needed to avoid
 
1303
        creating a real repository object locally.
 
1304
        """
 
1305
        self._ensure_real()
 
1306
        return self._real_repository.texts
 
1307
 
 
1308
    @needs_read_lock
 
1309
    def get_revisions(self, revision_ids):
 
1310
        self._ensure_real()
 
1311
        return self._real_repository.get_revisions(revision_ids)
 
1312
 
 
1313
    def supports_rich_root(self):
 
1314
        self._ensure_real()
 
1315
        return self._real_repository.supports_rich_root()
 
1316
 
 
1317
    def iter_reverse_revision_history(self, revision_id):
 
1318
        self._ensure_real()
 
1319
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
1320
 
 
1321
    @property
 
1322
    def _serializer(self):
 
1323
        self._ensure_real()
 
1324
        return self._real_repository._serializer
 
1325
 
 
1326
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1327
        self._ensure_real()
 
1328
        return self._real_repository.store_revision_signature(
 
1329
            gpg_strategy, plaintext, revision_id)
 
1330
 
 
1331
    def add_signature_text(self, revision_id, signature):
 
1332
        self._ensure_real()
 
1333
        return self._real_repository.add_signature_text(revision_id, signature)
 
1334
 
 
1335
    def has_signature_for_revision_id(self, revision_id):
 
1336
        self._ensure_real()
 
1337
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
1338
 
 
1339
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1340
        self._ensure_real()
 
1341
        return self._real_repository.item_keys_introduced_by(revision_ids,
 
1342
            _files_pb=_files_pb)
 
1343
 
 
1344
    def revision_graph_can_have_wrong_parents(self):
 
1345
        # The answer depends on the remote repo format.
 
1346
        self._ensure_real()
 
1347
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
1348
 
 
1349
    def _find_inconsistent_revision_parents(self):
 
1350
        self._ensure_real()
 
1351
        return self._real_repository._find_inconsistent_revision_parents()
 
1352
 
 
1353
    def _check_for_inconsistent_revision_parents(self):
 
1354
        self._ensure_real()
 
1355
        return self._real_repository._check_for_inconsistent_revision_parents()
 
1356
 
 
1357
    def _make_parents_provider(self, other=None):
 
1358
        providers = [self._unstacked_provider]
 
1359
        if other is not None:
 
1360
            providers.insert(0, other)
 
1361
        providers.extend(r._make_parents_provider() for r in
 
1362
                         self._fallback_repositories)
 
1363
        return graph._StackedParentsProvider(providers)
 
1364
 
 
1365
    def _serialise_search_recipe(self, recipe):
 
1366
        """Serialise a graph search recipe.
 
1367
 
 
1368
        :param recipe: A search recipe (start, stop, count).
 
1369
        :return: Serialised bytes.
 
1370
        """
 
1371
        start_keys = ' '.join(recipe[0])
 
1372
        stop_keys = ' '.join(recipe[1])
 
1373
        count = str(recipe[2])
 
1374
        return '\n'.join((start_keys, stop_keys, count))
 
1375
 
 
1376
    def autopack(self):
 
1377
        path = self.bzrdir._path_for_remote_call(self._client)
 
1378
        try:
 
1379
            response = self._call('PackRepository.autopack', path)
 
1380
        except errors.UnknownSmartMethod:
 
1381
            self._ensure_real()
 
1382
            self._real_repository._pack_collection.autopack()
 
1383
            return
 
1384
        if self._real_repository is not None:
 
1385
            # Reset the real repository's cache of pack names.
 
1386
            # XXX: At some point we may be able to skip this and just rely on
 
1387
            # the automatic retry logic to do the right thing, but for now we
 
1388
            # err on the side of being correct rather than being optimal.
 
1389
            self._real_repository._pack_collection.reload_pack_names()
 
1390
        if response[0] != 'ok':
 
1391
            raise errors.UnexpectedSmartServerResponse(response)
 
1392
 
 
1393
 
 
1394
class RemoteStreamSink(repository.StreamSink):
 
1395
 
 
1396
    def __init__(self, target_repo):
 
1397
        repository.StreamSink.__init__(self, target_repo)
 
1398
 
 
1399
    def _insert_real(self, stream, src_format, resume_tokens):
 
1400
        self.target_repo._ensure_real()
 
1401
        sink = self.target_repo._real_repository._get_sink()
 
1402
        result = sink.insert_stream(stream, src_format, resume_tokens)
 
1403
        if not result:
 
1404
            self.target_repo.autopack()
 
1405
        return result
 
1406
 
 
1407
    def insert_stream(self, stream, src_format, resume_tokens):
 
1408
        repo = self.target_repo
 
1409
        client = repo._client
 
1410
        medium = client._medium
 
1411
        if medium._is_remote_before((1, 13)):
 
1412
            # No possible way this can work.
 
1413
            return self._insert_real(stream, src_format, resume_tokens)
 
1414
        path = repo.bzrdir._path_for_remote_call(client)
 
1415
        if not resume_tokens:
 
1416
            # XXX: Ugly but important for correctness, *will* be fixed during
 
1417
            # 1.13 cycle. Pushing a stream that is interrupted results in a
 
1418
            # fallback to the _real_repositories sink *with a partial stream*.
 
1419
            # Thats bad because we insert less data than bzr expected. To avoid
 
1420
            # this we do a trial push to make sure the verb is accessible, and
 
1421
            # do not fallback when actually pushing the stream. A cleanup patch
 
1422
            # is going to look at rewinding/restarting the stream/partial
 
1423
            # buffering etc.
 
1424
            byte_stream = self._stream_to_byte_stream([], src_format)
 
1425
            try:
 
1426
                response = client.call_with_body_stream(
 
1427
                    ('Repository.insert_stream', path, ''), byte_stream)
 
1428
            except errors.UnknownSmartMethod:
 
1429
                medium._remember_remote_is_before((1,13))
 
1430
                return self._insert_real(stream, src_format, resume_tokens)
 
1431
        byte_stream = self._stream_to_byte_stream(stream, src_format)
 
1432
        resume_tokens = ' '.join(resume_tokens)
 
1433
        response = client.call_with_body_stream(
 
1434
            ('Repository.insert_stream', path, resume_tokens), byte_stream)
 
1435
        if response[0][0] not in ('ok', 'missing-basis'):
 
1436
            raise errors.UnexpectedSmartServerResponse(response)
 
1437
        if response[0][0] == 'missing-basis':
 
1438
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1439
            resume_tokens = tokens
 
1440
            return resume_tokens, missing_keys
 
1441
        else:
 
1442
            if self.target_repo._real_repository is not None:
 
1443
                collection = getattr(self.target_repo._real_repository,
 
1444
                    '_pack_collection', None)
 
1445
                if collection is not None:
 
1446
                    collection.reload_pack_names()
 
1447
            return [], set()
 
1448
 
 
1449
    def _stream_to_byte_stream(self, stream, src_format):
 
1450
        bytes = []
 
1451
        pack_writer = pack.ContainerWriter(bytes.append)
 
1452
        pack_writer.begin()
 
1453
        pack_writer.add_bytes_record(src_format.network_name(), '')
 
1454
        adapters = {}
 
1455
        def get_adapter(adapter_key):
 
1456
            try:
 
1457
                return adapters[adapter_key]
 
1458
            except KeyError:
 
1459
                adapter_factory = adapter_registry.get(adapter_key)
 
1460
                adapter = adapter_factory(self)
 
1461
                adapters[adapter_key] = adapter
 
1462
                return adapter
 
1463
        for substream_type, substream in stream:
 
1464
            for record in substream:
 
1465
                if record.storage_kind in ('chunked', 'fulltext'):
 
1466
                    serialised = record_to_fulltext_bytes(record)
 
1467
                else:
 
1468
                    serialised = record.get_bytes_as(record.storage_kind)
 
1469
                pack_writer.add_bytes_record(serialised, [(substream_type,)])
 
1470
                for b in bytes:
 
1471
                    yield b
 
1472
                del bytes[:]
 
1473
        pack_writer.end()
 
1474
        for b in bytes:
 
1475
            yield b
 
1476
 
 
1477
 
 
1478
class RemoteBranchLockableFiles(LockableFiles):
 
1479
    """A 'LockableFiles' implementation that talks to a smart server.
 
1480
 
 
1481
    This is not a public interface class.
 
1482
    """
 
1483
 
 
1484
    def __init__(self, bzrdir, _client):
 
1485
        self.bzrdir = bzrdir
 
1486
        self._client = _client
 
1487
        self._need_find_modes = True
 
1488
        LockableFiles.__init__(
 
1489
            self, bzrdir.get_branch_transport(None),
 
1490
            'lock', lockdir.LockDir)
 
1491
 
 
1492
    def _find_modes(self):
 
1493
        # RemoteBranches don't let the client set the mode of control files.
 
1494
        self._dir_mode = None
 
1495
        self._file_mode = None
 
1496
 
 
1497
 
 
1498
class RemoteBranchFormat(branch.BranchFormat):
 
1499
 
 
1500
    def __init__(self):
 
1501
        super(RemoteBranchFormat, self).__init__()
 
1502
        self._matchingbzrdir = RemoteBzrDirFormat()
 
1503
        self._matchingbzrdir.set_branch_format(self)
 
1504
        self._custom_format = None
 
1505
 
 
1506
    def __eq__(self, other):
 
1507
        return (isinstance(other, RemoteBranchFormat) and
 
1508
            self.__dict__ == other.__dict__)
 
1509
 
 
1510
    def get_format_description(self):
 
1511
        return 'Remote BZR Branch'
 
1512
 
 
1513
    def network_name(self):
 
1514
        return self._network_name
 
1515
 
 
1516
    def open(self, a_bzrdir):
 
1517
        return a_bzrdir.open_branch()
 
1518
 
 
1519
    def _vfs_initialize(self, a_bzrdir):
 
1520
        # Initialisation when using a local bzrdir object, or a non-vfs init
 
1521
        # method is not available on the server.
 
1522
        # self._custom_format is always set - the start of initialize ensures
 
1523
        # that.
 
1524
        if isinstance(a_bzrdir, RemoteBzrDir):
 
1525
            a_bzrdir._ensure_real()
 
1526
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
1527
        else:
 
1528
            # We assume the bzrdir is parameterised; it may not be.
 
1529
            result = self._custom_format.initialize(a_bzrdir)
 
1530
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
1531
            not isinstance(result, RemoteBranch)):
 
1532
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
1533
        return result
 
1534
 
 
1535
    def initialize(self, a_bzrdir):
 
1536
        # 1) get the network name to use.
 
1537
        if self._custom_format:
 
1538
            network_name = self._custom_format.network_name()
 
1539
        else:
 
1540
            # Select the current bzrlib default and ask for that.
 
1541
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
1542
            reference_format = reference_bzrdir_format.get_branch_format()
 
1543
            self._custom_format = reference_format
 
1544
            network_name = reference_format.network_name()
 
1545
        # Being asked to create on a non RemoteBzrDir:
 
1546
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
1547
            return self._vfs_initialize(a_bzrdir)
 
1548
        medium = a_bzrdir._client._medium
 
1549
        if medium._is_remote_before((1, 13)):
 
1550
            return self._vfs_initialize(a_bzrdir)
 
1551
        # Creating on a remote bzr dir.
 
1552
        # 2) try direct creation via RPC
 
1553
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
1554
        verb = 'BzrDir.create_branch'
 
1555
        try:
 
1556
            response = a_bzrdir._call(verb, path, network_name)
 
1557
        except errors.UnknownSmartMethod:
 
1558
            # Fallback - use vfs methods
 
1559
            return self._vfs_initialize(a_bzrdir)
 
1560
        if response[0] != 'ok':
 
1561
            raise errors.UnexpectedSmartServerResponse(response)
 
1562
        # Turn the response into a RemoteRepository object.
 
1563
        format = RemoteBranchFormat()
 
1564
        format._network_name = response[1]
 
1565
        repo_format = response_tuple_to_repo_format(response[3:])
 
1566
        if response[2] == '':
 
1567
            repo_bzrdir = a_bzrdir
 
1568
        else:
 
1569
            repo_bzrdir = RemoteBzrDir(
 
1570
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
1571
                a_bzrdir._client)
 
1572
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
1573
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
1574
            format=format, setup_stacking=False)
 
1575
        # XXX: We know this is a new branch, so it must have revno 0, revid
 
1576
        # NULL_REVISION. Creating the branch locked would make this be unable
 
1577
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
 
1578
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
 
1579
        return remote_branch
 
1580
 
 
1581
    def supports_tags(self):
 
1582
        # Remote branches might support tags, but we won't know until we
 
1583
        # access the real remote branch.
 
1584
        return True
 
1585
 
 
1586
 
 
1587
class RemoteBranch(branch.Branch, _RpcHelper):
 
1588
    """Branch stored on a server accessed by HPSS RPC.
 
1589
 
 
1590
    At the moment most operations are mapped down to simple file operations.
 
1591
    """
 
1592
 
 
1593
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
1594
        _client=None, format=None, setup_stacking=True):
 
1595
        """Create a RemoteBranch instance.
 
1596
 
 
1597
        :param real_branch: An optional local implementation of the branch
 
1598
            format, usually accessing the data via the VFS.
 
1599
        :param _client: Private parameter for testing.
 
1600
        :param format: A RemoteBranchFormat object, None to create one
 
1601
            automatically. If supplied it should have a network_name already
 
1602
            supplied.
 
1603
        :param setup_stacking: If True make an RPC call to determine the
 
1604
            stacked (or not) status of the branch. If False assume the branch
 
1605
            is not stacked.
 
1606
        """
 
1607
        # We intentionally don't call the parent class's __init__, because it
 
1608
        # will try to assign to self.tags, which is a property in this subclass.
 
1609
        # And the parent's __init__ doesn't do much anyway.
 
1610
        self._revision_id_to_revno_cache = None
 
1611
        self._partial_revision_id_to_revno_cache = {}
 
1612
        self._revision_history_cache = None
 
1613
        self._last_revision_info_cache = None
 
1614
        self._merge_sorted_revisions_cache = None
 
1615
        self.bzrdir = remote_bzrdir
 
1616
        if _client is not None:
 
1617
            self._client = _client
 
1618
        else:
 
1619
            self._client = remote_bzrdir._client
 
1620
        self.repository = remote_repository
 
1621
        if real_branch is not None:
 
1622
            self._real_branch = real_branch
 
1623
            # Give the remote repository the matching real repo.
 
1624
            real_repo = self._real_branch.repository
 
1625
            if isinstance(real_repo, RemoteRepository):
 
1626
                real_repo._ensure_real()
 
1627
                real_repo = real_repo._real_repository
 
1628
            self.repository._set_real_repository(real_repo)
 
1629
            # Give the branch the remote repository to let fast-pathing happen.
 
1630
            self._real_branch.repository = self.repository
 
1631
        else:
 
1632
            self._real_branch = None
 
1633
        # Fill out expected attributes of branch for bzrlib api users.
 
1634
        self.base = self.bzrdir.root_transport.base
 
1635
        self._control_files = None
 
1636
        self._lock_mode = None
 
1637
        self._lock_token = None
 
1638
        self._repo_lock_token = None
 
1639
        self._lock_count = 0
 
1640
        self._leave_lock = False
 
1641
        # Setup a format: note that we cannot call _ensure_real until all the
 
1642
        # attributes above are set: This code cannot be moved higher up in this
 
1643
        # function.
 
1644
        if format is None:
 
1645
            self._format = RemoteBranchFormat()
 
1646
            if real_branch is not None:
 
1647
                self._format._network_name = \
 
1648
                    self._real_branch._format.network_name()
 
1649
            #else:
 
1650
            #    # XXX: Need to get this from BzrDir.open_branch's return value.
 
1651
            #    self._ensure_real()
 
1652
            #    self._format._network_name = \
 
1653
            #        self._real_branch._format.network_name()
 
1654
        else:
 
1655
            self._format = format
 
1656
        # The base class init is not called, so we duplicate this:
 
1657
        hooks = branch.Branch.hooks['open']
 
1658
        for hook in hooks:
 
1659
            hook(self)
 
1660
        if setup_stacking:
 
1661
            self._setup_stacking()
 
1662
 
 
1663
    def _setup_stacking(self):
 
1664
        # configure stacking into the remote repository, by reading it from
 
1665
        # the vfs branch.
 
1666
        try:
 
1667
            fallback_url = self.get_stacked_on_url()
 
1668
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
1669
            errors.UnstackableRepositoryFormat), e:
 
1670
            return
 
1671
        # it's relative to this branch...
 
1672
        fallback_url = urlutils.join(self.base, fallback_url)
 
1673
        transports = [self.bzrdir.root_transport]
 
1674
        if self._real_branch is not None:
 
1675
            # The real repository is setup already:
 
1676
            transports.append(self._real_branch._transport)
 
1677
            self.repository.add_fallback_repository(
 
1678
                self.repository._real_repository._fallback_repositories[0])
 
1679
        else:
 
1680
            stacked_on = branch.Branch.open(fallback_url,
 
1681
                                            possible_transports=transports)
 
1682
            self.repository.add_fallback_repository(stacked_on.repository)
 
1683
 
 
1684
    def _get_real_transport(self):
 
1685
        # if we try vfs access, return the real branch's vfs transport
 
1686
        self._ensure_real()
 
1687
        return self._real_branch._transport
 
1688
 
 
1689
    _transport = property(_get_real_transport)
 
1690
 
 
1691
    def __str__(self):
 
1692
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
1693
 
 
1694
    __repr__ = __str__
 
1695
 
 
1696
    def _ensure_real(self):
 
1697
        """Ensure that there is a _real_branch set.
 
1698
 
 
1699
        Used before calls to self._real_branch.
 
1700
        """
 
1701
        if self._real_branch is None:
 
1702
            if not vfs.vfs_enabled():
 
1703
                raise AssertionError('smart server vfs must be enabled '
 
1704
                    'to use vfs implementation')
 
1705
            self.bzrdir._ensure_real()
 
1706
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
1707
            if self.repository._real_repository is None:
 
1708
                # Give the remote repository the matching real repo.
 
1709
                real_repo = self._real_branch.repository
 
1710
                if isinstance(real_repo, RemoteRepository):
 
1711
                    real_repo._ensure_real()
 
1712
                    real_repo = real_repo._real_repository
 
1713
                self.repository._set_real_repository(real_repo)
 
1714
            # Give the real branch the remote repository to let fast-pathing
 
1715
            # happen.
 
1716
            self._real_branch.repository = self.repository
 
1717
            if self._lock_mode == 'r':
 
1718
                self._real_branch.lock_read()
 
1719
            elif self._lock_mode == 'w':
 
1720
                self._real_branch.lock_write(token=self._lock_token)
 
1721
 
 
1722
    def _translate_error(self, err, **context):
 
1723
        self.repository._translate_error(err, branch=self, **context)
 
1724
 
 
1725
    def _clear_cached_state(self):
 
1726
        super(RemoteBranch, self)._clear_cached_state()
 
1727
        if self._real_branch is not None:
 
1728
            self._real_branch._clear_cached_state()
 
1729
 
 
1730
    def _clear_cached_state_of_remote_branch_only(self):
 
1731
        """Like _clear_cached_state, but doesn't clear the cache of
 
1732
        self._real_branch.
 
1733
 
 
1734
        This is useful when falling back to calling a method of
 
1735
        self._real_branch that changes state.  In that case the underlying
 
1736
        branch changes, so we need to invalidate this RemoteBranch's cache of
 
1737
        it.  However, there's no need to invalidate the _real_branch's cache
 
1738
        too, in fact doing so might harm performance.
 
1739
        """
 
1740
        super(RemoteBranch, self)._clear_cached_state()
 
1741
 
 
1742
    @property
 
1743
    def control_files(self):
 
1744
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
1745
        # because it triggers an _ensure_real that we otherwise might not need.
 
1746
        if self._control_files is None:
 
1747
            self._control_files = RemoteBranchLockableFiles(
 
1748
                self.bzrdir, self._client)
 
1749
        return self._control_files
 
1750
 
 
1751
    def _get_checkout_format(self):
 
1752
        self._ensure_real()
 
1753
        return self._real_branch._get_checkout_format()
 
1754
 
 
1755
    def get_physical_lock_status(self):
 
1756
        """See Branch.get_physical_lock_status()."""
 
1757
        # should be an API call to the server, as branches must be lockable.
 
1758
        self._ensure_real()
 
1759
        return self._real_branch.get_physical_lock_status()
 
1760
 
 
1761
    def get_stacked_on_url(self):
 
1762
        """Get the URL this branch is stacked against.
 
1763
 
 
1764
        :raises NotStacked: If the branch is not stacked.
 
1765
        :raises UnstackableBranchFormat: If the branch does not support
 
1766
            stacking.
 
1767
        :raises UnstackableRepositoryFormat: If the repository does not support
 
1768
            stacking.
 
1769
        """
 
1770
        try:
 
1771
            # there may not be a repository yet, so we can't use
 
1772
            # self._translate_error, so we can't use self._call either.
 
1773
            response = self._client.call('Branch.get_stacked_on_url',
 
1774
                self._remote_path())
 
1775
        except errors.ErrorFromSmartServer, err:
 
1776
            # there may not be a repository yet, so we can't call through
 
1777
            # its _translate_error
 
1778
            _translate_error(err, branch=self)
 
1779
        except errors.UnknownSmartMethod, err:
 
1780
            self._ensure_real()
 
1781
            return self._real_branch.get_stacked_on_url()
 
1782
        if response[0] != 'ok':
 
1783
            raise errors.UnexpectedSmartServerResponse(response)
 
1784
        return response[1]
 
1785
 
 
1786
    def lock_read(self):
 
1787
        self.repository.lock_read()
 
1788
        if not self._lock_mode:
 
1789
            self._lock_mode = 'r'
 
1790
            self._lock_count = 1
 
1791
            if self._real_branch is not None:
 
1792
                self._real_branch.lock_read()
 
1793
        else:
 
1794
            self._lock_count += 1
 
1795
 
 
1796
    def _remote_lock_write(self, token):
 
1797
        if token is None:
 
1798
            branch_token = repo_token = ''
 
1799
        else:
 
1800
            branch_token = token
 
1801
            repo_token = self.repository.lock_write()
 
1802
            self.repository.unlock()
 
1803
        err_context = {'token': token}
 
1804
        response = self._call(
 
1805
            'Branch.lock_write', self._remote_path(), branch_token,
 
1806
            repo_token or '', **err_context)
 
1807
        if response[0] != 'ok':
 
1808
            raise errors.UnexpectedSmartServerResponse(response)
 
1809
        ok, branch_token, repo_token = response
 
1810
        return branch_token, repo_token
 
1811
 
 
1812
    def lock_write(self, token=None):
 
1813
        if not self._lock_mode:
 
1814
            # Lock the branch and repo in one remote call.
 
1815
            remote_tokens = self._remote_lock_write(token)
 
1816
            self._lock_token, self._repo_lock_token = remote_tokens
 
1817
            if not self._lock_token:
 
1818
                raise SmartProtocolError('Remote server did not return a token!')
 
1819
            # Tell the self.repository object that it is locked.
 
1820
            self.repository.lock_write(
 
1821
                self._repo_lock_token, _skip_rpc=True)
 
1822
 
 
1823
            if self._real_branch is not None:
 
1824
                self._real_branch.lock_write(token=self._lock_token)
 
1825
            if token is not None:
 
1826
                self._leave_lock = True
 
1827
            else:
 
1828
                self._leave_lock = False
 
1829
            self._lock_mode = 'w'
 
1830
            self._lock_count = 1
 
1831
        elif self._lock_mode == 'r':
 
1832
            raise errors.ReadOnlyTransaction
 
1833
        else:
 
1834
            if token is not None:
 
1835
                # A token was given to lock_write, and we're relocking, so
 
1836
                # check that the given token actually matches the one we
 
1837
                # already have.
 
1838
                if token != self._lock_token:
 
1839
                    raise errors.TokenMismatch(token, self._lock_token)
 
1840
            self._lock_count += 1
 
1841
            # Re-lock the repository too.
 
1842
            self.repository.lock_write(self._repo_lock_token)
 
1843
        return self._lock_token or None
 
1844
 
 
1845
    def _unlock(self, branch_token, repo_token):
 
1846
        err_context = {'token': str((branch_token, repo_token))}
 
1847
        response = self._call(
 
1848
            'Branch.unlock', self._remote_path(), branch_token,
 
1849
            repo_token or '', **err_context)
 
1850
        if response == ('ok',):
 
1851
            return
 
1852
        raise errors.UnexpectedSmartServerResponse(response)
 
1853
 
 
1854
    def unlock(self):
 
1855
        try:
 
1856
            self._lock_count -= 1
 
1857
            if not self._lock_count:
 
1858
                self._clear_cached_state()
 
1859
                mode = self._lock_mode
 
1860
                self._lock_mode = None
 
1861
                if self._real_branch is not None:
 
1862
                    if (not self._leave_lock and mode == 'w' and
 
1863
                        self._repo_lock_token):
 
1864
                        # If this RemoteBranch will remove the physical lock
 
1865
                        # for the repository, make sure the _real_branch
 
1866
                        # doesn't do it first.  (Because the _real_branch's
 
1867
                        # repository is set to be the RemoteRepository.)
 
1868
                        self._real_branch.repository.leave_lock_in_place()
 
1869
                    self._real_branch.unlock()
 
1870
                if mode != 'w':
 
1871
                    # Only write-locked branched need to make a remote method
 
1872
                    # call to perfom the unlock.
 
1873
                    return
 
1874
                if not self._lock_token:
 
1875
                    raise AssertionError('Locked, but no token!')
 
1876
                branch_token = self._lock_token
 
1877
                repo_token = self._repo_lock_token
 
1878
                self._lock_token = None
 
1879
                self._repo_lock_token = None
 
1880
                if not self._leave_lock:
 
1881
                    self._unlock(branch_token, repo_token)
 
1882
        finally:
 
1883
            self.repository.unlock()
 
1884
 
 
1885
    def break_lock(self):
 
1886
        self._ensure_real()
 
1887
        return self._real_branch.break_lock()
 
1888
 
 
1889
    def leave_lock_in_place(self):
 
1890
        if not self._lock_token:
 
1891
            raise NotImplementedError(self.leave_lock_in_place)
 
1892
        self._leave_lock = True
 
1893
 
 
1894
    def dont_leave_lock_in_place(self):
 
1895
        if not self._lock_token:
 
1896
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1897
        self._leave_lock = False
 
1898
 
 
1899
    def _last_revision_info(self):
 
1900
        response = self._call('Branch.last_revision_info', self._remote_path())
 
1901
        if response[0] != 'ok':
 
1902
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1903
        revno = int(response[1])
 
1904
        last_revision = response[2]
 
1905
        return (revno, last_revision)
 
1906
 
 
1907
    def _gen_revision_history(self):
 
1908
        """See Branch._gen_revision_history()."""
 
1909
        response_tuple, response_handler = self._call_expecting_body(
 
1910
            'Branch.revision_history', self._remote_path())
 
1911
        if response_tuple[0] != 'ok':
 
1912
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1913
        result = response_handler.read_body_bytes().split('\x00')
 
1914
        if result == ['']:
 
1915
            return []
 
1916
        return result
 
1917
 
 
1918
    def _remote_path(self):
 
1919
        return self.bzrdir._path_for_remote_call(self._client)
 
1920
 
 
1921
    def _set_last_revision_descendant(self, revision_id, other_branch,
 
1922
            allow_diverged=False, allow_overwrite_descendant=False):
 
1923
        # This performs additional work to meet the hook contract; while its
 
1924
        # undesirable, we have to synthesise the revno to call the hook, and
 
1925
        # not calling the hook is worse as it means changes can't be prevented.
 
1926
        # Having calculated this though, we can't just call into
 
1927
        # set_last_revision_info as a simple call, because there is a set_rh
 
1928
        # hook that some folk may still be using.
 
1929
        old_revno, old_revid = self.last_revision_info()
 
1930
        history = self._lefthand_history(revision_id)
 
1931
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
1932
        err_context = {'other_branch': other_branch}
 
1933
        response = self._call('Branch.set_last_revision_ex',
 
1934
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
1935
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
1936
            **err_context)
 
1937
        self._clear_cached_state()
 
1938
        if len(response) != 3 and response[0] != 'ok':
 
1939
            raise errors.UnexpectedSmartServerResponse(response)
 
1940
        new_revno, new_revision_id = response[1:]
 
1941
        self._last_revision_info_cache = new_revno, new_revision_id
 
1942
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
1943
        if self._real_branch is not None:
 
1944
            cache = new_revno, new_revision_id
 
1945
            self._real_branch._last_revision_info_cache = cache
 
1946
 
 
1947
    def _set_last_revision(self, revision_id):
 
1948
        old_revno, old_revid = self.last_revision_info()
 
1949
        # This performs additional work to meet the hook contract; while its
 
1950
        # undesirable, we have to synthesise the revno to call the hook, and
 
1951
        # not calling the hook is worse as it means changes can't be prevented.
 
1952
        # Having calculated this though, we can't just call into
 
1953
        # set_last_revision_info as a simple call, because there is a set_rh
 
1954
        # hook that some folk may still be using.
 
1955
        history = self._lefthand_history(revision_id)
 
1956
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
1957
        self._clear_cached_state()
 
1958
        response = self._call('Branch.set_last_revision',
 
1959
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
1960
            revision_id)
 
1961
        if response != ('ok',):
 
1962
            raise errors.UnexpectedSmartServerResponse(response)
 
1963
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
1964
 
 
1965
    @needs_write_lock
 
1966
    def set_revision_history(self, rev_history):
 
1967
        # Send just the tip revision of the history; the server will generate
 
1968
        # the full history from that.  If the revision doesn't exist in this
 
1969
        # branch, NoSuchRevision will be raised.
 
1970
        if rev_history == []:
 
1971
            rev_id = 'null:'
 
1972
        else:
 
1973
            rev_id = rev_history[-1]
 
1974
        self._set_last_revision(rev_id)
 
1975
        for hook in branch.Branch.hooks['set_rh']:
 
1976
            hook(self, rev_history)
 
1977
        self._cache_revision_history(rev_history)
 
1978
 
 
1979
    def get_parent(self):
 
1980
        self._ensure_real()
 
1981
        return self._real_branch.get_parent()
 
1982
 
 
1983
    def _get_parent_location(self):
 
1984
        # Used by tests, when checking normalisation of given vs stored paths.
 
1985
        self._ensure_real()
 
1986
        return self._real_branch._get_parent_location()
 
1987
 
 
1988
    def set_parent(self, url):
 
1989
        self._ensure_real()
 
1990
        return self._real_branch.set_parent(url)
 
1991
 
 
1992
    def _set_parent_location(self, url):
 
1993
        # Used by tests, to poke bad urls into branch configurations
 
1994
        if url is None:
 
1995
            self.set_parent(url)
 
1996
        else:
 
1997
            self._ensure_real()
 
1998
            return self._real_branch._set_parent_location(url)
 
1999
 
 
2000
    def set_stacked_on_url(self, stacked_location):
 
2001
        """Set the URL this branch is stacked against.
 
2002
 
 
2003
        :raises UnstackableBranchFormat: If the branch does not support
 
2004
            stacking.
 
2005
        :raises UnstackableRepositoryFormat: If the repository does not support
 
2006
            stacking.
 
2007
        """
 
2008
        self._ensure_real()
 
2009
        return self._real_branch.set_stacked_on_url(stacked_location)
 
2010
 
 
2011
    @needs_write_lock
 
2012
    def pull(self, source, overwrite=False, stop_revision=None,
 
2013
             **kwargs):
 
2014
        self._clear_cached_state_of_remote_branch_only()
 
2015
        self._ensure_real()
 
2016
        return self._real_branch.pull(
 
2017
            source, overwrite=overwrite, stop_revision=stop_revision,
 
2018
            _override_hook_target=self, **kwargs)
 
2019
 
 
2020
    @needs_read_lock
 
2021
    def push(self, target, overwrite=False, stop_revision=None):
 
2022
        self._ensure_real()
 
2023
        return self._real_branch.push(
 
2024
            target, overwrite=overwrite, stop_revision=stop_revision,
 
2025
            _override_hook_source_branch=self)
 
2026
 
 
2027
    def is_locked(self):
 
2028
        return self._lock_count >= 1
 
2029
 
 
2030
    @needs_read_lock
 
2031
    def revision_id_to_revno(self, revision_id):
 
2032
        self._ensure_real()
 
2033
        return self._real_branch.revision_id_to_revno(revision_id)
 
2034
 
 
2035
    @needs_write_lock
 
2036
    def set_last_revision_info(self, revno, revision_id):
 
2037
        # XXX: These should be returned by the set_last_revision_info verb
 
2038
        old_revno, old_revid = self.last_revision_info()
 
2039
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2040
        revision_id = ensure_null(revision_id)
 
2041
        try:
 
2042
            response = self._call('Branch.set_last_revision_info',
 
2043
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2044
                str(revno), revision_id)
 
2045
        except errors.UnknownSmartMethod:
 
2046
            self._ensure_real()
 
2047
            self._clear_cached_state_of_remote_branch_only()
 
2048
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2049
            self._last_revision_info_cache = revno, revision_id
 
2050
            return
 
2051
        if response == ('ok',):
 
2052
            self._clear_cached_state()
 
2053
            self._last_revision_info_cache = revno, revision_id
 
2054
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2055
            # Update the _real_branch's cache too.
 
2056
            if self._real_branch is not None:
 
2057
                cache = self._last_revision_info_cache
 
2058
                self._real_branch._last_revision_info_cache = cache
 
2059
        else:
 
2060
            raise errors.UnexpectedSmartServerResponse(response)
 
2061
 
 
2062
    @needs_write_lock
 
2063
    def generate_revision_history(self, revision_id, last_rev=None,
 
2064
                                  other_branch=None):
 
2065
        medium = self._client._medium
 
2066
        if not medium._is_remote_before((1, 6)):
 
2067
            # Use a smart method for 1.6 and above servers
 
2068
            try:
 
2069
                self._set_last_revision_descendant(revision_id, other_branch,
 
2070
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2071
                return
 
2072
            except errors.UnknownSmartMethod:
 
2073
                medium._remember_remote_is_before((1, 6))
 
2074
        self._clear_cached_state_of_remote_branch_only()
 
2075
        self.set_revision_history(self._lefthand_history(revision_id,
 
2076
            last_rev=last_rev,other_branch=other_branch))
 
2077
 
 
2078
    @property
 
2079
    def tags(self):
 
2080
        self._ensure_real()
 
2081
        return self._real_branch.tags
 
2082
 
 
2083
    def set_push_location(self, location):
 
2084
        self._ensure_real()
 
2085
        return self._real_branch.set_push_location(location)
 
2086
 
 
2087
 
 
2088
def _extract_tar(tar, to_dir):
 
2089
    """Extract all the contents of a tarfile object.
 
2090
 
 
2091
    A replacement for extractall, which is not present in python2.4
 
2092
    """
 
2093
    for tarinfo in tar:
 
2094
        tar.extract(tarinfo, to_dir)
 
2095
 
 
2096
 
 
2097
def _translate_error(err, **context):
 
2098
    """Translate an ErrorFromSmartServer into a more useful error.
 
2099
 
 
2100
    Possible context keys:
 
2101
      - branch
 
2102
      - repository
 
2103
      - bzrdir
 
2104
      - token
 
2105
      - other_branch
 
2106
      - path
 
2107
 
 
2108
    If the error from the server doesn't match a known pattern, then
 
2109
    UnknownErrorFromSmartServer is raised.
 
2110
    """
 
2111
    def find(name):
 
2112
        try:
 
2113
            return context[name]
 
2114
        except KeyError, key_err:
 
2115
            mutter('Missing key %r in context %r', key_err.args[0], context)
 
2116
            raise err
 
2117
    def get_path():
 
2118
        """Get the path from the context if present, otherwise use first error
 
2119
        arg.
 
2120
        """
 
2121
        try:
 
2122
            return context['path']
 
2123
        except KeyError, key_err:
 
2124
            try:
 
2125
                return err.error_args[0]
 
2126
            except IndexError, idx_err:
 
2127
                mutter(
 
2128
                    'Missing key %r in context %r', key_err.args[0], context)
 
2129
                raise err
 
2130
 
 
2131
    if err.error_verb == 'NoSuchRevision':
 
2132
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2133
    elif err.error_verb == 'nosuchrevision':
 
2134
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2135
    elif err.error_tuple == ('nobranch',):
 
2136
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2137
    elif err.error_verb == 'norepository':
 
2138
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2139
    elif err.error_verb == 'LockContention':
 
2140
        raise errors.LockContention('(remote lock)')
 
2141
    elif err.error_verb == 'UnlockableTransport':
 
2142
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2143
    elif err.error_verb == 'LockFailed':
 
2144
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2145
    elif err.error_verb == 'TokenMismatch':
 
2146
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2147
    elif err.error_verb == 'Diverged':
 
2148
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2149
    elif err.error_verb == 'TipChangeRejected':
 
2150
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2151
    elif err.error_verb == 'UnstackableBranchFormat':
 
2152
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2153
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2154
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2155
    elif err.error_verb == 'NotStacked':
 
2156
        raise errors.NotStacked(branch=find('branch'))
 
2157
    elif err.error_verb == 'PermissionDenied':
 
2158
        path = get_path()
 
2159
        if len(err.error_args) >= 2:
 
2160
            extra = err.error_args[1]
 
2161
        else:
 
2162
            extra = None
 
2163
        raise errors.PermissionDenied(path, extra=extra)
 
2164
    elif err.error_verb == 'ReadError':
 
2165
        path = get_path()
 
2166
        raise errors.ReadError(path)
 
2167
    elif err.error_verb == 'NoSuchFile':
 
2168
        path = get_path()
 
2169
        raise errors.NoSuchFile(path)
 
2170
    elif err.error_verb == 'FileExists':
 
2171
        raise errors.FileExists(err.error_args[0])
 
2172
    elif err.error_verb == 'DirectoryNotEmpty':
 
2173
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2174
    elif err.error_verb == 'ShortReadvError':
 
2175
        args = err.error_args
 
2176
        raise errors.ShortReadvError(
 
2177
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2178
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2179
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2180
        val = err.error_args[1]
 
2181
        start = int(err.error_args[2])
 
2182
        end = int(err.error_args[3])
 
2183
        reason = str(err.error_args[4]) # reason must always be a string
 
2184
        if val.startswith('u:'):
 
2185
            val = val[2:].decode('utf-8')
 
2186
        elif val.startswith('s:'):
 
2187
            val = val[2:].decode('base64')
 
2188
        if err.error_verb == 'UnicodeDecodeError':
 
2189
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2190
        elif err.error_verb == 'UnicodeEncodeError':
 
2191
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
2192
    elif err.error_verb == 'ReadOnlyError':
 
2193
        raise errors.TransportNotPossible('readonly transport')
 
2194
    raise errors.UnknownErrorFromSmartServer(err)