/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: John Arbash Meinel
  • Date: 2009-02-25 21:13:22 UTC
  • mto: This revision was merged to the branch mainline in revision 4051.
  • Revision ID: john@arbash-meinel.com-20090225211322-qc94czk3s1g7nliq
Some direct tests for _group_keys_for_io

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
        self._resume_tokens = []
 
1368
 
 
1369
    def _insert_real(self, stream, src_format):
 
1370
        self.target_repo._ensure_real()
 
1371
        sink = self.target_repo._real_repository._get_sink()
 
1372
        result = sink.insert_stream(stream, src_format)
 
1373
        if not result:
 
1374
            self.target_repo.autopack()
 
1375
        return result
 
1376
 
 
1377
    def insert_stream(self, stream, src_format):
 
1378
        repo = self.target_repo
 
1379
        client = repo._client
 
1380
        medium = client._medium
 
1381
        if medium._is_remote_before((1, 13)):
 
1382
            # No possible way this can work.
 
1383
            return self._insert_real(stream, src_format)
 
1384
        path = repo.bzrdir._path_for_remote_call(client)
 
1385
        if not self._resume_tokens:
 
1386
            # XXX: Ugly but important for correctness, *will* be fixed during
 
1387
            # 1.13 cycle. Pushing a stream that is interrupted results in a
 
1388
            # fallback to the _real_repositories sink *with a partial stream*.
 
1389
            # Thats bad because we insert less data than bzr expected. To avoid
 
1390
            # this we do a trial push to make sure the verb is accessible, and
 
1391
            # do not fallback when actually pushing the stream. A cleanup patch
 
1392
            # is going to look at rewinding/restarting the stream/partial
 
1393
            # buffering etc.
 
1394
            byte_stream = self._stream_to_byte_stream([], src_format)
 
1395
            try:
 
1396
                resume_tokens = ''
 
1397
                response = client.call_with_body_stream(
 
1398
                    ('Repository.insert_stream', path, resume_tokens), byte_stream)
 
1399
            except errors.UnknownSmartMethod:
 
1400
                medium._remember_remote_is_before((1,13))
 
1401
                return self._insert_real(stream, src_format)
 
1402
        byte_stream = self._stream_to_byte_stream(stream, src_format)
 
1403
        resume_tokens = ' '.join(self._resume_tokens)
 
1404
        response = client.call_with_body_stream(
 
1405
            ('Repository.insert_stream', path, resume_tokens), byte_stream)
 
1406
        if response[0][0] not in ('ok', 'missing-basis'):
 
1407
            raise errors.UnexpectedSmartServerResponse(response)
 
1408
        if response[0][0] == 'missing-basis':
 
1409
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1410
            self._resume_tokens = tokens
 
1411
            return missing_keys
 
1412
        else:
 
1413
            if self.target_repo._real_repository is not None:
 
1414
                collection = getattr(self.target_repo._real_repository,
 
1415
                    '_pack_collection', None)
 
1416
                if collection is not None:
 
1417
                    collection.reload_pack_names()
 
1418
            return []
 
1419
 
 
1420
    def _stream_to_byte_stream(self, stream, src_format):
 
1421
        bytes = []
 
1422
        pack_writer = pack.ContainerWriter(bytes.append)
 
1423
        pack_writer.begin()
 
1424
        pack_writer.add_bytes_record(src_format.network_name(), '')
 
1425
        adapters = {}
 
1426
        def get_adapter(adapter_key):
 
1427
            try:
 
1428
                return adapters[adapter_key]
 
1429
            except KeyError:
 
1430
                adapter_factory = adapter_registry.get(adapter_key)
 
1431
                adapter = adapter_factory(self)
 
1432
                adapters[adapter_key] = adapter
 
1433
                return adapter
 
1434
        for substream_type, substream in stream:
 
1435
            for record in substream:
 
1436
                if record.storage_kind in ('chunked', 'fulltext'):
 
1437
                    serialised = record_to_fulltext_bytes(record)
 
1438
                else:
 
1439
                    serialised = record.get_bytes_as(record.storage_kind)
 
1440
                pack_writer.add_bytes_record(serialised, [(substream_type,)])
 
1441
                for b in bytes:
 
1442
                    yield b
 
1443
                del bytes[:]
 
1444
        pack_writer.end()
 
1445
        for b in bytes:
 
1446
            yield b
 
1447
 
 
1448
 
 
1449
class RemoteBranchLockableFiles(LockableFiles):
 
1450
    """A 'LockableFiles' implementation that talks to a smart server.
 
1451
 
 
1452
    This is not a public interface class.
 
1453
    """
 
1454
 
 
1455
    def __init__(self, bzrdir, _client):
 
1456
        self.bzrdir = bzrdir
 
1457
        self._client = _client
 
1458
        self._need_find_modes = True
 
1459
        LockableFiles.__init__(
 
1460
            self, bzrdir.get_branch_transport(None),
 
1461
            'lock', lockdir.LockDir)
 
1462
 
 
1463
    def _find_modes(self):
 
1464
        # RemoteBranches don't let the client set the mode of control files.
 
1465
        self._dir_mode = None
 
1466
        self._file_mode = None
 
1467
 
 
1468
 
 
1469
class RemoteBranchFormat(branch.BranchFormat):
 
1470
 
 
1471
    def __init__(self):
 
1472
        super(RemoteBranchFormat, self).__init__()
 
1473
        self._matchingbzrdir = RemoteBzrDirFormat()
 
1474
        self._matchingbzrdir.set_branch_format(self)
 
1475
        self._custom_format = None
 
1476
 
 
1477
    def __eq__(self, other):
 
1478
        return (isinstance(other, RemoteBranchFormat) and
 
1479
            self.__dict__ == other.__dict__)
 
1480
 
 
1481
    def get_format_description(self):
 
1482
        return 'Remote BZR Branch'
 
1483
 
 
1484
    def network_name(self):
 
1485
        return self._network_name
 
1486
 
 
1487
    def open(self, a_bzrdir):
 
1488
        return a_bzrdir.open_branch()
 
1489
 
 
1490
    def _vfs_initialize(self, a_bzrdir):
 
1491
        # Initialisation when using a local bzrdir object, or a non-vfs init
 
1492
        # method is not available on the server.
 
1493
        # self._custom_format is always set - the start of initialize ensures
 
1494
        # that.
 
1495
        if isinstance(a_bzrdir, RemoteBzrDir):
 
1496
            a_bzrdir._ensure_real()
 
1497
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
1498
        else:
 
1499
            # We assume the bzrdir is parameterised; it may not be.
 
1500
            result = self._custom_format.initialize(a_bzrdir)
 
1501
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
1502
            not isinstance(result, RemoteBranch)):
 
1503
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
1504
        return result
 
1505
 
 
1506
    def initialize(self, a_bzrdir):
 
1507
        # 1) get the network name to use.
 
1508
        if self._custom_format:
 
1509
            network_name = self._custom_format.network_name()
 
1510
        else:
 
1511
            # Select the current bzrlib default and ask for that.
 
1512
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
1513
            reference_format = reference_bzrdir_format.get_branch_format()
 
1514
            self._custom_format = reference_format
 
1515
            network_name = reference_format.network_name()
 
1516
        # Being asked to create on a non RemoteBzrDir:
 
1517
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
1518
            return self._vfs_initialize(a_bzrdir)
 
1519
        medium = a_bzrdir._client._medium
 
1520
        if medium._is_remote_before((1, 13)):
 
1521
            return self._vfs_initialize(a_bzrdir)
 
1522
        # Creating on a remote bzr dir.
 
1523
        # 2) try direct creation via RPC
 
1524
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
1525
        verb = 'BzrDir.create_branch'
 
1526
        try:
 
1527
            response = a_bzrdir._call(verb, path, network_name)
 
1528
        except errors.UnknownSmartMethod:
 
1529
            # Fallback - use vfs methods
 
1530
            return self._vfs_initialize(a_bzrdir)
 
1531
        if response[0] != 'ok':
 
1532
            raise errors.UnexpectedSmartServerResponse(response)
 
1533
        # Turn the response into a RemoteRepository object.
 
1534
        format = RemoteBranchFormat()
 
1535
        format._network_name = response[1]
 
1536
        repo_format = response_tuple_to_repo_format(response[3:])
 
1537
        if response[2] == '':
 
1538
            repo_bzrdir = a_bzrdir
 
1539
        else:
 
1540
            repo_bzrdir = RemoteBzrDir(
 
1541
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
1542
                a_bzrdir._client)
 
1543
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
1544
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
1545
            format=format, setup_stacking=False)
 
1546
        return remote_branch
 
1547
 
 
1548
    def supports_tags(self):
 
1549
        # Remote branches might support tags, but we won't know until we
 
1550
        # access the real remote branch.
 
1551
        return True
 
1552
 
 
1553
 
 
1554
class RemoteBranch(branch.Branch, _RpcHelper):
 
1555
    """Branch stored on a server accessed by HPSS RPC.
 
1556
 
 
1557
    At the moment most operations are mapped down to simple file operations.
 
1558
    """
 
1559
 
 
1560
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
1561
        _client=None, format=None, setup_stacking=True):
 
1562
        """Create a RemoteBranch instance.
 
1563
 
 
1564
        :param real_branch: An optional local implementation of the branch
 
1565
            format, usually accessing the data via the VFS.
 
1566
        :param _client: Private parameter for testing.
 
1567
        :param format: A RemoteBranchFormat object, None to create one
 
1568
            automatically. If supplied it should have a network_name already
 
1569
            supplied.
 
1570
        :param setup_stacking: If True make an RPC call to determine the
 
1571
            stacked (or not) status of the branch. If False assume the branch
 
1572
            is not stacked.
 
1573
        """
 
1574
        # We intentionally don't call the parent class's __init__, because it
 
1575
        # will try to assign to self.tags, which is a property in this subclass.
 
1576
        # And the parent's __init__ doesn't do much anyway.
 
1577
        self._revision_id_to_revno_cache = None
 
1578
        self._partial_revision_id_to_revno_cache = {}
 
1579
        self._revision_history_cache = None
 
1580
        self._last_revision_info_cache = None
 
1581
        self._merge_sorted_revisions_cache = None
 
1582
        self.bzrdir = remote_bzrdir
 
1583
        if _client is not None:
 
1584
            self._client = _client
 
1585
        else:
 
1586
            self._client = remote_bzrdir._client
 
1587
        self.repository = remote_repository
 
1588
        if real_branch is not None:
 
1589
            self._real_branch = real_branch
 
1590
            # Give the remote repository the matching real repo.
 
1591
            real_repo = self._real_branch.repository
 
1592
            if isinstance(real_repo, RemoteRepository):
 
1593
                real_repo._ensure_real()
 
1594
                real_repo = real_repo._real_repository
 
1595
            self.repository._set_real_repository(real_repo)
 
1596
            # Give the branch the remote repository to let fast-pathing happen.
 
1597
            self._real_branch.repository = self.repository
 
1598
        else:
 
1599
            self._real_branch = None
 
1600
        # Fill out expected attributes of branch for bzrlib api users.
 
1601
        self.base = self.bzrdir.root_transport.base
 
1602
        self._control_files = None
 
1603
        self._lock_mode = None
 
1604
        self._lock_token = None
 
1605
        self._repo_lock_token = None
 
1606
        self._lock_count = 0
 
1607
        self._leave_lock = False
 
1608
        # Setup a format: note that we cannot call _ensure_real until all the
 
1609
        # attributes above are set: This code cannot be moved higher up in this
 
1610
        # function.
 
1611
        if format is None:
 
1612
            self._format = RemoteBranchFormat()
 
1613
            if real_branch is not None:
 
1614
                self._format._network_name = \
 
1615
                    self._real_branch._format.network_name()
 
1616
            #else:
 
1617
            #    # XXX: Need to get this from BzrDir.open_branch's return value.
 
1618
            #    self._ensure_real()
 
1619
            #    self._format._network_name = \
 
1620
            #        self._real_branch._format.network_name()
 
1621
        else:
 
1622
            self._format = format
 
1623
        # The base class init is not called, so we duplicate this:
 
1624
        hooks = branch.Branch.hooks['open']
 
1625
        for hook in hooks:
 
1626
            hook(self)
 
1627
        if setup_stacking:
 
1628
            self._setup_stacking()
 
1629
 
 
1630
    def _setup_stacking(self):
 
1631
        # configure stacking into the remote repository, by reading it from
 
1632
        # the vfs branch.
 
1633
        try:
 
1634
            fallback_url = self.get_stacked_on_url()
 
1635
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
1636
            errors.UnstackableRepositoryFormat), e:
 
1637
            return
 
1638
        # it's relative to this branch...
 
1639
        fallback_url = urlutils.join(self.base, fallback_url)
 
1640
        transports = [self.bzrdir.root_transport]
 
1641
        if self._real_branch is not None:
 
1642
            transports.append(self._real_branch._transport)
 
1643
        stacked_on = branch.Branch.open(fallback_url,
 
1644
                                        possible_transports=transports)
 
1645
        self.repository.add_fallback_repository(stacked_on.repository)
 
1646
 
 
1647
    def _get_real_transport(self):
 
1648
        # if we try vfs access, return the real branch's vfs transport
 
1649
        self._ensure_real()
 
1650
        return self._real_branch._transport
 
1651
 
 
1652
    _transport = property(_get_real_transport)
 
1653
 
 
1654
    def __str__(self):
 
1655
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
1656
 
 
1657
    __repr__ = __str__
 
1658
 
 
1659
    def _ensure_real(self):
 
1660
        """Ensure that there is a _real_branch set.
 
1661
 
 
1662
        Used before calls to self._real_branch.
 
1663
        """
 
1664
        if self._real_branch is None:
 
1665
            if not vfs.vfs_enabled():
 
1666
                raise AssertionError('smart server vfs must be enabled '
 
1667
                    'to use vfs implementation')
 
1668
            self.bzrdir._ensure_real()
 
1669
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
1670
            if self.repository._real_repository is None:
 
1671
                # Give the remote repository the matching real repo.
 
1672
                real_repo = self._real_branch.repository
 
1673
                if isinstance(real_repo, RemoteRepository):
 
1674
                    real_repo._ensure_real()
 
1675
                    real_repo = real_repo._real_repository
 
1676
                self.repository._set_real_repository(real_repo)
 
1677
            # Give the real branch the remote repository to let fast-pathing
 
1678
            # happen.
 
1679
            self._real_branch.repository = self.repository
 
1680
            if self._lock_mode == 'r':
 
1681
                self._real_branch.lock_read()
 
1682
            elif self._lock_mode == 'w':
 
1683
                self._real_branch.lock_write(token=self._lock_token)
 
1684
 
 
1685
    def _translate_error(self, err, **context):
 
1686
        self.repository._translate_error(err, branch=self, **context)
 
1687
 
 
1688
    def _clear_cached_state(self):
 
1689
        super(RemoteBranch, self)._clear_cached_state()
 
1690
        if self._real_branch is not None:
 
1691
            self._real_branch._clear_cached_state()
 
1692
 
 
1693
    def _clear_cached_state_of_remote_branch_only(self):
 
1694
        """Like _clear_cached_state, but doesn't clear the cache of
 
1695
        self._real_branch.
 
1696
 
 
1697
        This is useful when falling back to calling a method of
 
1698
        self._real_branch that changes state.  In that case the underlying
 
1699
        branch changes, so we need to invalidate this RemoteBranch's cache of
 
1700
        it.  However, there's no need to invalidate the _real_branch's cache
 
1701
        too, in fact doing so might harm performance.
 
1702
        """
 
1703
        super(RemoteBranch, self)._clear_cached_state()
 
1704
 
 
1705
    @property
 
1706
    def control_files(self):
 
1707
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
1708
        # because it triggers an _ensure_real that we otherwise might not need.
 
1709
        if self._control_files is None:
 
1710
            self._control_files = RemoteBranchLockableFiles(
 
1711
                self.bzrdir, self._client)
 
1712
        return self._control_files
 
1713
 
 
1714
    def _get_checkout_format(self):
 
1715
        self._ensure_real()
 
1716
        return self._real_branch._get_checkout_format()
 
1717
 
 
1718
    def get_physical_lock_status(self):
 
1719
        """See Branch.get_physical_lock_status()."""
 
1720
        # should be an API call to the server, as branches must be lockable.
 
1721
        self._ensure_real()
 
1722
        return self._real_branch.get_physical_lock_status()
 
1723
 
 
1724
    def get_stacked_on_url(self):
 
1725
        """Get the URL this branch is stacked against.
 
1726
 
 
1727
        :raises NotStacked: If the branch is not stacked.
 
1728
        :raises UnstackableBranchFormat: If the branch does not support
 
1729
            stacking.
 
1730
        :raises UnstackableRepositoryFormat: If the repository does not support
 
1731
            stacking.
 
1732
        """
 
1733
        try:
 
1734
            # there may not be a repository yet, so we can't use
 
1735
            # self._translate_error, so we can't use self._call either.
 
1736
            response = self._client.call('Branch.get_stacked_on_url',
 
1737
                self._remote_path())
 
1738
        except errors.ErrorFromSmartServer, err:
 
1739
            # there may not be a repository yet, so we can't call through
 
1740
            # its _translate_error
 
1741
            _translate_error(err, branch=self)
 
1742
        except errors.UnknownSmartMethod, err:
 
1743
            self._ensure_real()
 
1744
            return self._real_branch.get_stacked_on_url()
 
1745
        if response[0] != 'ok':
 
1746
            raise errors.UnexpectedSmartServerResponse(response)
 
1747
        return response[1]
 
1748
 
 
1749
    def lock_read(self):
 
1750
        self.repository.lock_read()
 
1751
        if not self._lock_mode:
 
1752
            self._lock_mode = 'r'
 
1753
            self._lock_count = 1
 
1754
            if self._real_branch is not None:
 
1755
                self._real_branch.lock_read()
 
1756
        else:
 
1757
            self._lock_count += 1
 
1758
 
 
1759
    def _remote_lock_write(self, token):
 
1760
        if token is None:
 
1761
            branch_token = repo_token = ''
 
1762
        else:
 
1763
            branch_token = token
 
1764
            repo_token = self.repository.lock_write()
 
1765
            self.repository.unlock()
 
1766
        err_context = {'token': token}
 
1767
        response = self._call(
 
1768
            'Branch.lock_write', self._remote_path(), branch_token,
 
1769
            repo_token or '', **err_context)
 
1770
        if response[0] != 'ok':
 
1771
            raise errors.UnexpectedSmartServerResponse(response)
 
1772
        ok, branch_token, repo_token = response
 
1773
        return branch_token, repo_token
 
1774
 
 
1775
    def lock_write(self, token=None):
 
1776
        if not self._lock_mode:
 
1777
            # Lock the branch and repo in one remote call.
 
1778
            remote_tokens = self._remote_lock_write(token)
 
1779
            self._lock_token, self._repo_lock_token = remote_tokens
 
1780
            if not self._lock_token:
 
1781
                raise SmartProtocolError('Remote server did not return a token!')
 
1782
            # Tell the self.repository object that it is locked.
 
1783
            self.repository.lock_write(
 
1784
                self._repo_lock_token, _skip_rpc=True)
 
1785
 
 
1786
            if self._real_branch is not None:
 
1787
                self._real_branch.lock_write(token=self._lock_token)
 
1788
            if token is not None:
 
1789
                self._leave_lock = True
 
1790
            else:
 
1791
                self._leave_lock = False
 
1792
            self._lock_mode = 'w'
 
1793
            self._lock_count = 1
 
1794
        elif self._lock_mode == 'r':
 
1795
            raise errors.ReadOnlyTransaction
 
1796
        else:
 
1797
            if token is not None:
 
1798
                # A token was given to lock_write, and we're relocking, so
 
1799
                # check that the given token actually matches the one we
 
1800
                # already have.
 
1801
                if token != self._lock_token:
 
1802
                    raise errors.TokenMismatch(token, self._lock_token)
 
1803
            self._lock_count += 1
 
1804
            # Re-lock the repository too.
 
1805
            self.repository.lock_write(self._repo_lock_token)
 
1806
        return self._lock_token or None
 
1807
 
 
1808
    def _unlock(self, branch_token, repo_token):
 
1809
        err_context = {'token': str((branch_token, repo_token))}
 
1810
        response = self._call(
 
1811
            'Branch.unlock', self._remote_path(), branch_token,
 
1812
            repo_token or '', **err_context)
 
1813
        if response == ('ok',):
 
1814
            return
 
1815
        raise errors.UnexpectedSmartServerResponse(response)
 
1816
 
 
1817
    def unlock(self):
 
1818
        try:
 
1819
            self._lock_count -= 1
 
1820
            if not self._lock_count:
 
1821
                self._clear_cached_state()
 
1822
                mode = self._lock_mode
 
1823
                self._lock_mode = None
 
1824
                if self._real_branch is not None:
 
1825
                    if (not self._leave_lock and mode == 'w' and
 
1826
                        self._repo_lock_token):
 
1827
                        # If this RemoteBranch will remove the physical lock
 
1828
                        # for the repository, make sure the _real_branch
 
1829
                        # doesn't do it first.  (Because the _real_branch's
 
1830
                        # repository is set to be the RemoteRepository.)
 
1831
                        self._real_branch.repository.leave_lock_in_place()
 
1832
                    self._real_branch.unlock()
 
1833
                if mode != 'w':
 
1834
                    # Only write-locked branched need to make a remote method
 
1835
                    # call to perfom the unlock.
 
1836
                    return
 
1837
                if not self._lock_token:
 
1838
                    raise AssertionError('Locked, but no token!')
 
1839
                branch_token = self._lock_token
 
1840
                repo_token = self._repo_lock_token
 
1841
                self._lock_token = None
 
1842
                self._repo_lock_token = None
 
1843
                if not self._leave_lock:
 
1844
                    self._unlock(branch_token, repo_token)
 
1845
        finally:
 
1846
            self.repository.unlock()
 
1847
 
 
1848
    def break_lock(self):
 
1849
        self._ensure_real()
 
1850
        return self._real_branch.break_lock()
 
1851
 
 
1852
    def leave_lock_in_place(self):
 
1853
        if not self._lock_token:
 
1854
            raise NotImplementedError(self.leave_lock_in_place)
 
1855
        self._leave_lock = True
 
1856
 
 
1857
    def dont_leave_lock_in_place(self):
 
1858
        if not self._lock_token:
 
1859
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1860
        self._leave_lock = False
 
1861
 
 
1862
    def _last_revision_info(self):
 
1863
        response = self._call('Branch.last_revision_info', self._remote_path())
 
1864
        if response[0] != 'ok':
 
1865
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1866
        revno = int(response[1])
 
1867
        last_revision = response[2]
 
1868
        return (revno, last_revision)
 
1869
 
 
1870
    def _gen_revision_history(self):
 
1871
        """See Branch._gen_revision_history()."""
 
1872
        response_tuple, response_handler = self._call_expecting_body(
 
1873
            'Branch.revision_history', self._remote_path())
 
1874
        if response_tuple[0] != 'ok':
 
1875
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1876
        result = response_handler.read_body_bytes().split('\x00')
 
1877
        if result == ['']:
 
1878
            return []
 
1879
        return result
 
1880
 
 
1881
    def _remote_path(self):
 
1882
        return self.bzrdir._path_for_remote_call(self._client)
 
1883
 
 
1884
    def _set_last_revision_descendant(self, revision_id, other_branch,
 
1885
            allow_diverged=False, allow_overwrite_descendant=False):
 
1886
        # This performs additional work to meet the hook contract; while its
 
1887
        # undesirable, we have to synthesise the revno to call the hook, and
 
1888
        # not calling the hook is worse as it means changes can't be prevented.
 
1889
        # Having calculated this though, we can't just call into
 
1890
        # set_last_revision_info as a simple call, because there is a set_rh
 
1891
        # hook that some folk may still be using.
 
1892
        old_revno, old_revid = self.last_revision_info()
 
1893
        history = self._lefthand_history(revision_id)
 
1894
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
1895
        err_context = {'other_branch': other_branch}
 
1896
        response = self._call('Branch.set_last_revision_ex',
 
1897
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
1898
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
1899
            **err_context)
 
1900
        self._clear_cached_state()
 
1901
        if len(response) != 3 and response[0] != 'ok':
 
1902
            raise errors.UnexpectedSmartServerResponse(response)
 
1903
        new_revno, new_revision_id = response[1:]
 
1904
        self._last_revision_info_cache = new_revno, new_revision_id
 
1905
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
1906
        if self._real_branch is not None:
 
1907
            cache = new_revno, new_revision_id
 
1908
            self._real_branch._last_revision_info_cache = cache
 
1909
 
 
1910
    def _set_last_revision(self, revision_id):
 
1911
        old_revno, old_revid = self.last_revision_info()
 
1912
        # This performs additional work to meet the hook contract; while its
 
1913
        # undesirable, we have to synthesise the revno to call the hook, and
 
1914
        # not calling the hook is worse as it means changes can't be prevented.
 
1915
        # Having calculated this though, we can't just call into
 
1916
        # set_last_revision_info as a simple call, because there is a set_rh
 
1917
        # hook that some folk may still be using.
 
1918
        history = self._lefthand_history(revision_id)
 
1919
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
1920
        self._clear_cached_state()
 
1921
        response = self._call('Branch.set_last_revision',
 
1922
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
1923
            revision_id)
 
1924
        if response != ('ok',):
 
1925
            raise errors.UnexpectedSmartServerResponse(response)
 
1926
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
1927
 
 
1928
    @needs_write_lock
 
1929
    def set_revision_history(self, rev_history):
 
1930
        # Send just the tip revision of the history; the server will generate
 
1931
        # the full history from that.  If the revision doesn't exist in this
 
1932
        # branch, NoSuchRevision will be raised.
 
1933
        if rev_history == []:
 
1934
            rev_id = 'null:'
 
1935
        else:
 
1936
            rev_id = rev_history[-1]
 
1937
        self._set_last_revision(rev_id)
 
1938
        for hook in branch.Branch.hooks['set_rh']:
 
1939
            hook(self, rev_history)
 
1940
        self._cache_revision_history(rev_history)
 
1941
 
 
1942
    def get_parent(self):
 
1943
        self._ensure_real()
 
1944
        return self._real_branch.get_parent()
 
1945
 
 
1946
    def _get_parent_location(self):
 
1947
        # Used by tests, when checking normalisation of given vs stored paths.
 
1948
        self._ensure_real()
 
1949
        return self._real_branch._get_parent_location()
 
1950
 
 
1951
    def set_parent(self, url):
 
1952
        self._ensure_real()
 
1953
        return self._real_branch.set_parent(url)
 
1954
 
 
1955
    def _set_parent_location(self, url):
 
1956
        # Used by tests, to poke bad urls into branch configurations
 
1957
        if url is None:
 
1958
            self.set_parent(url)
 
1959
        else:
 
1960
            self._ensure_real()
 
1961
            return self._real_branch._set_parent_location(url)
 
1962
 
 
1963
    def set_stacked_on_url(self, stacked_location):
 
1964
        """Set the URL this branch is stacked against.
 
1965
 
 
1966
        :raises UnstackableBranchFormat: If the branch does not support
 
1967
            stacking.
 
1968
        :raises UnstackableRepositoryFormat: If the repository does not support
 
1969
            stacking.
 
1970
        """
 
1971
        self._ensure_real()
 
1972
        return self._real_branch.set_stacked_on_url(stacked_location)
 
1973
 
 
1974
    @needs_write_lock
 
1975
    def pull(self, source, overwrite=False, stop_revision=None,
 
1976
             **kwargs):
 
1977
        self._clear_cached_state_of_remote_branch_only()
 
1978
        self._ensure_real()
 
1979
        return self._real_branch.pull(
 
1980
            source, overwrite=overwrite, stop_revision=stop_revision,
 
1981
            _override_hook_target=self, **kwargs)
 
1982
 
 
1983
    @needs_read_lock
 
1984
    def push(self, target, overwrite=False, stop_revision=None):
 
1985
        self._ensure_real()
 
1986
        return self._real_branch.push(
 
1987
            target, overwrite=overwrite, stop_revision=stop_revision,
 
1988
            _override_hook_source_branch=self)
 
1989
 
 
1990
    def is_locked(self):
 
1991
        return self._lock_count >= 1
 
1992
 
 
1993
    @needs_read_lock
 
1994
    def revision_id_to_revno(self, revision_id):
 
1995
        self._ensure_real()
 
1996
        return self._real_branch.revision_id_to_revno(revision_id)
 
1997
 
 
1998
    @needs_write_lock
 
1999
    def set_last_revision_info(self, revno, revision_id):
 
2000
        # XXX: These should be returned by the set_last_revision_info verb
 
2001
        old_revno, old_revid = self.last_revision_info()
 
2002
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2003
        revision_id = ensure_null(revision_id)
 
2004
        try:
 
2005
            response = self._call('Branch.set_last_revision_info',
 
2006
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2007
                str(revno), revision_id)
 
2008
        except errors.UnknownSmartMethod:
 
2009
            self._ensure_real()
 
2010
            self._clear_cached_state_of_remote_branch_only()
 
2011
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2012
            self._last_revision_info_cache = revno, revision_id
 
2013
            return
 
2014
        if response == ('ok',):
 
2015
            self._clear_cached_state()
 
2016
            self._last_revision_info_cache = revno, revision_id
 
2017
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2018
            # Update the _real_branch's cache too.
 
2019
            if self._real_branch is not None:
 
2020
                cache = self._last_revision_info_cache
 
2021
                self._real_branch._last_revision_info_cache = cache
 
2022
        else:
 
2023
            raise errors.UnexpectedSmartServerResponse(response)
 
2024
 
 
2025
    @needs_write_lock
 
2026
    def generate_revision_history(self, revision_id, last_rev=None,
 
2027
                                  other_branch=None):
 
2028
        medium = self._client._medium
 
2029
        if not medium._is_remote_before((1, 6)):
 
2030
            # Use a smart method for 1.6 and above servers
 
2031
            try:
 
2032
                self._set_last_revision_descendant(revision_id, other_branch,
 
2033
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2034
                return
 
2035
            except errors.UnknownSmartMethod:
 
2036
                medium._remember_remote_is_before((1, 6))
 
2037
        self._clear_cached_state_of_remote_branch_only()
 
2038
        self.set_revision_history(self._lefthand_history(revision_id,
 
2039
            last_rev=last_rev,other_branch=other_branch))
 
2040
 
 
2041
    @property
 
2042
    def tags(self):
 
2043
        self._ensure_real()
 
2044
        return self._real_branch.tags
 
2045
 
 
2046
    def set_push_location(self, location):
 
2047
        self._ensure_real()
 
2048
        return self._real_branch.set_push_location(location)
 
2049
 
 
2050
    @needs_write_lock
 
2051
    def update_revisions(self, other, stop_revision=None, overwrite=False,
 
2052
                         graph=None):
 
2053
        """See Branch.update_revisions."""
 
2054
        other.lock_read()
 
2055
        try:
 
2056
            if stop_revision is None:
 
2057
                stop_revision = other.last_revision()
 
2058
                if revision.is_null(stop_revision):
 
2059
                    # if there are no commits, we're done.
 
2060
                    return
 
2061
            self.fetch(other, stop_revision)
 
2062
 
 
2063
            if overwrite:
 
2064
                # Just unconditionally set the new revision.  We don't care if
 
2065
                # the branches have diverged.
 
2066
                self._set_last_revision(stop_revision)
 
2067
            else:
 
2068
                medium = self._client._medium
 
2069
                if not medium._is_remote_before((1, 6)):
 
2070
                    try:
 
2071
                        self._set_last_revision_descendant(stop_revision, other)
 
2072
                        return
 
2073
                    except errors.UnknownSmartMethod:
 
2074
                        medium._remember_remote_is_before((1, 6))
 
2075
                # Fallback for pre-1.6 servers: check for divergence
 
2076
                # client-side, then do _set_last_revision.
 
2077
                last_rev = revision.ensure_null(self.last_revision())
 
2078
                if graph is None:
 
2079
                    graph = self.repository.get_graph()
 
2080
                if self._check_if_descendant_or_diverged(
 
2081
                        stop_revision, last_rev, graph, other):
 
2082
                    # stop_revision is a descendant of last_rev, but we aren't
 
2083
                    # overwriting, so we're done.
 
2084
                    return
 
2085
                self._set_last_revision(stop_revision)
 
2086
        finally:
 
2087
            other.unlock()
 
2088
 
 
2089
 
 
2090
def _extract_tar(tar, to_dir):
 
2091
    """Extract all the contents of a tarfile object.
 
2092
 
 
2093
    A replacement for extractall, which is not present in python2.4
 
2094
    """
 
2095
    for tarinfo in tar:
 
2096
        tar.extract(tarinfo, to_dir)
 
2097
 
 
2098
 
 
2099
def _translate_error(err, **context):
 
2100
    """Translate an ErrorFromSmartServer into a more useful error.
 
2101
 
 
2102
    Possible context keys:
 
2103
      - branch
 
2104
      - repository
 
2105
      - bzrdir
 
2106
      - token
 
2107
      - other_branch
 
2108
      - path
 
2109
 
 
2110
    If the error from the server doesn't match a known pattern, then
 
2111
    UnknownErrorFromSmartServer is raised.
 
2112
    """
 
2113
    def find(name):
 
2114
        try:
 
2115
            return context[name]
 
2116
        except KeyError, key_err:
 
2117
            mutter('Missing key %r in context %r', key_err.args[0], context)
 
2118
            raise err
 
2119
    def get_path():
 
2120
        """Get the path from the context if present, otherwise use first error
 
2121
        arg.
 
2122
        """
 
2123
        try:
 
2124
            return context['path']
 
2125
        except KeyError, key_err:
 
2126
            try:
 
2127
                return err.error_args[0]
 
2128
            except IndexError, idx_err:
 
2129
                mutter(
 
2130
                    'Missing key %r in context %r', key_err.args[0], context)
 
2131
                raise err
 
2132
 
 
2133
    if err.error_verb == 'NoSuchRevision':
 
2134
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2135
    elif err.error_verb == 'nosuchrevision':
 
2136
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2137
    elif err.error_tuple == ('nobranch',):
 
2138
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2139
    elif err.error_verb == 'norepository':
 
2140
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2141
    elif err.error_verb == 'LockContention':
 
2142
        raise errors.LockContention('(remote lock)')
 
2143
    elif err.error_verb == 'UnlockableTransport':
 
2144
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2145
    elif err.error_verb == 'LockFailed':
 
2146
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2147
    elif err.error_verb == 'TokenMismatch':
 
2148
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2149
    elif err.error_verb == 'Diverged':
 
2150
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2151
    elif err.error_verb == 'TipChangeRejected':
 
2152
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2153
    elif err.error_verb == 'UnstackableBranchFormat':
 
2154
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2155
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2156
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2157
    elif err.error_verb == 'NotStacked':
 
2158
        raise errors.NotStacked(branch=find('branch'))
 
2159
    elif err.error_verb == 'PermissionDenied':
 
2160
        path = get_path()
 
2161
        if len(err.error_args) >= 2:
 
2162
            extra = err.error_args[1]
 
2163
        else:
 
2164
            extra = None
 
2165
        raise errors.PermissionDenied(path, extra=extra)
 
2166
    elif err.error_verb == 'ReadError':
 
2167
        path = get_path()
 
2168
        raise errors.ReadError(path)
 
2169
    elif err.error_verb == 'NoSuchFile':
 
2170
        path = get_path()
 
2171
        raise errors.NoSuchFile(path)
 
2172
    elif err.error_verb == 'FileExists':
 
2173
        raise errors.FileExists(err.error_args[0])
 
2174
    elif err.error_verb == 'DirectoryNotEmpty':
 
2175
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2176
    elif err.error_verb == 'ShortReadvError':
 
2177
        args = err.error_args
 
2178
        raise errors.ShortReadvError(
 
2179
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2180
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2181
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2182
        val = err.error_args[1]
 
2183
        start = int(err.error_args[2])
 
2184
        end = int(err.error_args[3])
 
2185
        reason = str(err.error_args[4]) # reason must always be a string
 
2186
        if val.startswith('u:'):
 
2187
            val = val[2:].decode('utf-8')
 
2188
        elif val.startswith('s:'):
 
2189
            val = val[2:].decode('base64')
 
2190
        if err.error_verb == 'UnicodeDecodeError':
 
2191
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2192
        elif err.error_verb == 'UnicodeEncodeError':
 
2193
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
2194
    elif err.error_verb == 'ReadOnlyError':
 
2195
        raise errors.TransportNotPossible('readonly transport')
 
2196
    raise errors.UnknownErrorFromSmartServer(err)