/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: Martin Pool
  • Date: 2009-03-03 01:43:55 UTC
  • mto: (4070.4.5 gnu-changelog)
  • mto: This revision was merged to the branch mainline in revision 4081.
  • Revision ID: mbp@sourcefrog.net-20090303014355-st0wve4ka9a5tcem
BranchBuilder now takes a timestamp for commits

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