/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: Andrew Bennetts
  • Date: 2009-03-06 08:13:23 UTC
  • mto: (4086.1.2 hpss-integration)
  • mto: This revision was merged to the branch mainline in revision 4087.
  • Revision ID: andrew.bennetts@canonical.com-20090306081323-dunguef2eok4wyti
Tweaks requested by Robert's review.

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