/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2009-02-26 04:25:00 UTC
  • mto: This revision was merged to the branch mainline in revision 4060.
  • Revision ID: robertc@robertcollins.net-20090226042500-xl9urv7zcbvkfjvs
New version of the BzrDir.find_repository verb supporting _network_name to support removing more _ensure_real calls.

Show diffs side-by-side

added added

removed removed

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