/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2009-02-24 21:22:43 UTC
  • mto: This revision was merged to the branch mainline in revision 4044.
  • Revision ID: robertc@robertcollins.net-20090224212243-wpleuzp5uin2h89p
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.

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