/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2009-03-03 06:29:28 UTC
  • mto: This revision was merged to the branch mainline in revision 4075.
  • Revision ID: robertc@robertcollins.net-20090303062928-q5akblb37uc0vzmu
MissedĀ oneĀ test.

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