/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: 2010-01-11 05:58:22 UTC
  • mto: This revision was merged to the branch mainline in revision 4965.
  • Revision ID: andrew.bennetts@canonical.com-20100111055822-npdkf88i86i1g54h
Fix HPSS tests; pass 'location is a repository' message via smart server when possible (adds BzrDir.open_branchV3 verb).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import bz2
 
18
 
 
19
from bzrlib import (
 
20
    bencode,
 
21
    branch,
 
22
    bzrdir,
 
23
    config,
 
24
    debug,
 
25
    errors,
 
26
    graph,
 
27
    lock,
 
28
    lockdir,
 
29
    repository,
 
30
    revision,
 
31
    revision as _mod_revision,
 
32
    symbol_versioning,
 
33
)
 
34
from bzrlib.branch import BranchReferenceFormat
 
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
37
from bzrlib.errors import (
 
38
    NoSuchRevision,
 
39
    SmartProtocolError,
 
40
    )
 
41
from bzrlib.lockable_files import LockableFiles
 
42
from bzrlib.smart import client, vfs, repository as smart_repo
 
43
from bzrlib.revision import ensure_null, NULL_REVISION
 
44
from bzrlib.trace import mutter, note, warning
 
45
 
 
46
 
 
47
class _RpcHelper(object):
 
48
    """Mixin class that helps with issuing RPCs."""
 
49
 
 
50
    def _call(self, method, *args, **err_context):
 
51
        try:
 
52
            return self._client.call(method, *args)
 
53
        except errors.ErrorFromSmartServer, err:
 
54
            self._translate_error(err, **err_context)
 
55
 
 
56
    def _call_expecting_body(self, method, *args, **err_context):
 
57
        try:
 
58
            return self._client.call_expecting_body(method, *args)
 
59
        except errors.ErrorFromSmartServer, err:
 
60
            self._translate_error(err, **err_context)
 
61
 
 
62
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
 
63
        try:
 
64
            return self._client.call_with_body_bytes(method, args, body_bytes)
 
65
        except errors.ErrorFromSmartServer, err:
 
66
            self._translate_error(err, **err_context)
 
67
 
 
68
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
 
69
                                             **err_context):
 
70
        try:
 
71
            return self._client.call_with_body_bytes_expecting_body(
 
72
                method, args, body_bytes)
 
73
        except errors.ErrorFromSmartServer, err:
 
74
            self._translate_error(err, **err_context)
 
75
 
 
76
 
 
77
def response_tuple_to_repo_format(response):
 
78
    """Convert a response tuple describing a repository format to a format."""
 
79
    format = RemoteRepositoryFormat()
 
80
    format._rich_root_data = (response[0] == 'yes')
 
81
    format._supports_tree_reference = (response[1] == 'yes')
 
82
    format._supports_external_lookups = (response[2] == 'yes')
 
83
    format._network_name = response[3]
 
84
    return format
 
85
 
 
86
 
 
87
# Note: RemoteBzrDirFormat is in bzrdir.py
 
88
 
 
89
class RemoteBzrDir(BzrDir, _RpcHelper):
 
90
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
91
 
 
92
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
93
        """Construct a RemoteBzrDir.
 
94
 
 
95
        :param _client: Private parameter for testing. Disables probing and the
 
96
            use of a real bzrdir.
 
97
        """
 
98
        BzrDir.__init__(self, transport, format)
 
99
        # this object holds a delegated bzrdir that uses file-level operations
 
100
        # to talk to the other side
 
101
        self._real_bzrdir = None
 
102
        self._has_working_tree = None
 
103
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
 
104
        # create_branch for details.
 
105
        self._next_open_branch_result = None
 
106
 
 
107
        if _client is None:
 
108
            medium = transport.get_smart_medium()
 
109
            self._client = client._SmartClient(medium)
 
110
        else:
 
111
            self._client = _client
 
112
            if not _force_probe:
 
113
                return
 
114
 
 
115
        self._probe_bzrdir()
 
116
 
 
117
    def _probe_bzrdir(self):
 
118
        medium = self._client._medium
 
119
        path = self._path_for_remote_call(self._client)
 
120
        if medium._is_remote_before((2, 1)):
 
121
            self._rpc_open(path)
 
122
            return
 
123
        try:
 
124
            self._rpc_open_2_1(path)
 
125
            return
 
126
        except errors.UnknownSmartMethod:
 
127
            medium._remember_remote_is_before((2, 1))
 
128
            self._rpc_open(path)
 
129
 
 
130
    def _rpc_open_2_1(self, path):
 
131
        response = self._call('BzrDir.open_2.1', path)
 
132
        if response == ('no',):
 
133
            raise errors.NotBranchError(path=self.root_transport.base)
 
134
        elif response[0] == 'yes':
 
135
            if response[1] == 'yes':
 
136
                self._has_working_tree = True
 
137
            elif response[1] == 'no':
 
138
                self._has_working_tree = False
 
139
            else:
 
140
                raise errors.UnexpectedSmartServerResponse(response)
 
141
        else:
 
142
            raise errors.UnexpectedSmartServerResponse(response)
 
143
 
 
144
    def _rpc_open(self, path):
 
145
        response = self._call('BzrDir.open', path)
 
146
        if response not in [('yes',), ('no',)]:
 
147
            raise errors.UnexpectedSmartServerResponse(response)
 
148
        if response == ('no',):
 
149
            raise errors.NotBranchError(path=self.root_transport.base)
 
150
 
 
151
    def _ensure_real(self):
 
152
        """Ensure that there is a _real_bzrdir set.
 
153
 
 
154
        Used before calls to self._real_bzrdir.
 
155
        """
 
156
        if not self._real_bzrdir:
 
157
            if 'hpssvfs' in debug.debug_flags:
 
158
                import traceback
 
159
                warning('VFS BzrDir access triggered\n%s',
 
160
                    ''.join(traceback.format_stack()))
 
161
            self._real_bzrdir = BzrDir.open_from_transport(
 
162
                self.root_transport, _server_formats=False)
 
163
            self._format._network_name = \
 
164
                self._real_bzrdir._format.network_name()
 
165
 
 
166
    def _translate_error(self, err, **context):
 
167
        _translate_error(err, bzrdir=self, **context)
 
168
 
 
169
    def break_lock(self):
 
170
        # Prevent aliasing problems in the next_open_branch_result cache.
 
171
        # See create_branch for rationale.
 
172
        self._next_open_branch_result = None
 
173
        return BzrDir.break_lock(self)
 
174
 
 
175
    def _vfs_cloning_metadir(self, require_stacking=False):
 
176
        self._ensure_real()
 
177
        return self._real_bzrdir.cloning_metadir(
 
178
            require_stacking=require_stacking)
 
179
 
 
180
    def cloning_metadir(self, require_stacking=False):
 
181
        medium = self._client._medium
 
182
        if medium._is_remote_before((1, 13)):
 
183
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
184
        verb = 'BzrDir.cloning_metadir'
 
185
        if require_stacking:
 
186
            stacking = 'True'
 
187
        else:
 
188
            stacking = 'False'
 
189
        path = self._path_for_remote_call(self._client)
 
190
        try:
 
191
            response = self._call(verb, path, stacking)
 
192
        except errors.UnknownSmartMethod:
 
193
            medium._remember_remote_is_before((1, 13))
 
194
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
195
        except errors.UnknownErrorFromSmartServer, err:
 
196
            if err.error_tuple != ('BranchReference',):
 
197
                raise
 
198
            # We need to resolve the branch reference to determine the
 
199
            # cloning_metadir.  This causes unnecessary RPCs to open the
 
200
            # referenced branch (and bzrdir, etc) but only when the caller
 
201
            # didn't already resolve the branch reference.
 
202
            referenced_branch = self.open_branch()
 
203
            return referenced_branch.bzrdir.cloning_metadir()
 
204
        if len(response) != 3:
 
205
            raise errors.UnexpectedSmartServerResponse(response)
 
206
        control_name, repo_name, branch_info = response
 
207
        if len(branch_info) != 2:
 
208
            raise errors.UnexpectedSmartServerResponse(response)
 
209
        branch_ref, branch_name = branch_info
 
210
        format = bzrdir.network_format_registry.get(control_name)
 
211
        if repo_name:
 
212
            format.repository_format = repository.network_format_registry.get(
 
213
                repo_name)
 
214
        if branch_ref == 'ref':
 
215
            # XXX: we need possible_transports here to avoid reopening the
 
216
            # connection to the referenced location
 
217
            ref_bzrdir = BzrDir.open(branch_name)
 
218
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
 
219
            format.set_branch_format(branch_format)
 
220
        elif branch_ref == 'branch':
 
221
            if branch_name:
 
222
                format.set_branch_format(
 
223
                    branch.network_format_registry.get(branch_name))
 
224
        else:
 
225
            raise errors.UnexpectedSmartServerResponse(response)
 
226
        return format
 
227
 
 
228
    def create_repository(self, shared=False):
 
229
        # as per meta1 formats - just delegate to the format object which may
 
230
        # be parameterised.
 
231
        result = self._format.repository_format.initialize(self, shared)
 
232
        if not isinstance(result, RemoteRepository):
 
233
            return self.open_repository()
 
234
        else:
 
235
            return result
 
236
 
 
237
    def destroy_repository(self):
 
238
        """See BzrDir.destroy_repository"""
 
239
        self._ensure_real()
 
240
        self._real_bzrdir.destroy_repository()
 
241
 
 
242
    def create_branch(self):
 
243
        # as per meta1 formats - just delegate to the format object which may
 
244
        # be parameterised.
 
245
        real_branch = self._format.get_branch_format().initialize(self)
 
246
        if not isinstance(real_branch, RemoteBranch):
 
247
            result = RemoteBranch(self, self.find_repository(), real_branch)
 
248
        else:
 
249
            result = real_branch
 
250
        # BzrDir.clone_on_transport() uses the result of create_branch but does
 
251
        # not return it to its callers; we save approximately 8% of our round
 
252
        # trips by handing the branch we created back to the first caller to
 
253
        # open_branch rather than probing anew. Long term we need a API in
 
254
        # bzrdir that doesn't discard result objects (like result_branch).
 
255
        # RBC 20090225
 
256
        self._next_open_branch_result = result
 
257
        return result
 
258
 
 
259
    def destroy_branch(self):
 
260
        """See BzrDir.destroy_branch"""
 
261
        self._ensure_real()
 
262
        self._real_bzrdir.destroy_branch()
 
263
        self._next_open_branch_result = None
 
264
 
 
265
    def create_workingtree(self, revision_id=None, from_branch=None):
 
266
        raise errors.NotLocalUrl(self.transport.base)
 
267
 
 
268
    def find_branch_format(self):
 
269
        """Find the branch 'format' for this bzrdir.
 
270
 
 
271
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
272
        """
 
273
        b = self.open_branch()
 
274
        return b._format
 
275
 
 
276
    def get_branch_reference(self):
 
277
        """See BzrDir.get_branch_reference()."""
 
278
        response = self._get_branch_reference()
 
279
        if response[0] == 'ref':
 
280
            return response[1]
 
281
        else:
 
282
            return None
 
283
 
 
284
    def _get_branch_reference(self):
 
285
        path = self._path_for_remote_call(self._client)
 
286
        medium = self._client._medium
 
287
        candidate_calls = [
 
288
            ('BzrDir.open_branchV3', (2, 1)),
 
289
            ('BzrDir.open_branchV2', (1, 13)),
 
290
            ('BzrDir.open_branch', None),
 
291
            ]
 
292
        for verb, required_version in candidate_calls:
 
293
            if required_version and medium._is_remote_before(required_version):
 
294
                continue
 
295
            try:
 
296
                response = self._call(verb, path)
 
297
            except errors.UnknownSmartMethod:
 
298
                if required_version is None:
 
299
                    raise
 
300
                medium._remember_remote_is_before(required_version)
 
301
            else:
 
302
                break
 
303
        if verb == 'BzrDir.open_branch':
 
304
            if response[0] != 'ok':
 
305
                raise errors.UnexpectedSmartServerResponse(response)
 
306
            if response[1] != '':
 
307
                return ('ref', response[1])
 
308
            else:
 
309
                return ('branch', '')
 
310
        if response[0] not in ('ref', 'branch'):
 
311
            raise errors.UnexpectedSmartServerResponse(response)
 
312
        return response
 
313
 
 
314
    def _get_tree_branch(self):
 
315
        """See BzrDir._get_tree_branch()."""
 
316
        return None, self.open_branch()
 
317
 
 
318
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
 
319
        if _unsupported:
 
320
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
321
        if self._next_open_branch_result is not None:
 
322
            # See create_branch for details.
 
323
            result = self._next_open_branch_result
 
324
            self._next_open_branch_result = None
 
325
            return result
 
326
        response = self._get_branch_reference()
 
327
        if response[0] == 'ref':
 
328
            # a branch reference, use the existing BranchReference logic.
 
329
            format = BranchReferenceFormat()
 
330
            return format.open(self, _found=True, location=response[1],
 
331
                ignore_fallbacks=ignore_fallbacks)
 
332
        branch_format_name = response[1]
 
333
        if not branch_format_name:
 
334
            branch_format_name = None
 
335
        format = RemoteBranchFormat(network_name=branch_format_name)
 
336
        return RemoteBranch(self, self.find_repository(), format=format,
 
337
            setup_stacking=not ignore_fallbacks)
 
338
 
 
339
    def _open_repo_v1(self, path):
 
340
        verb = 'BzrDir.find_repository'
 
341
        response = self._call(verb, path)
 
342
        if response[0] != 'ok':
 
343
            raise errors.UnexpectedSmartServerResponse(response)
 
344
        # servers that only support the v1 method don't support external
 
345
        # references either.
 
346
        self._ensure_real()
 
347
        repo = self._real_bzrdir.open_repository()
 
348
        response = response + ('no', repo._format.network_name())
 
349
        return response, repo
 
350
 
 
351
    def _open_repo_v2(self, path):
 
352
        verb = 'BzrDir.find_repositoryV2'
 
353
        response = self._call(verb, path)
 
354
        if response[0] != 'ok':
 
355
            raise errors.UnexpectedSmartServerResponse(response)
 
356
        self._ensure_real()
 
357
        repo = self._real_bzrdir.open_repository()
 
358
        response = response + (repo._format.network_name(),)
 
359
        return response, repo
 
360
 
 
361
    def _open_repo_v3(self, path):
 
362
        verb = 'BzrDir.find_repositoryV3'
 
363
        medium = self._client._medium
 
364
        if medium._is_remote_before((1, 13)):
 
365
            raise errors.UnknownSmartMethod(verb)
 
366
        try:
 
367
            response = self._call(verb, path)
 
368
        except errors.UnknownSmartMethod:
 
369
            medium._remember_remote_is_before((1, 13))
 
370
            raise
 
371
        if response[0] != 'ok':
 
372
            raise errors.UnexpectedSmartServerResponse(response)
 
373
        return response, None
 
374
 
 
375
    def open_repository(self):
 
376
        path = self._path_for_remote_call(self._client)
 
377
        response = None
 
378
        for probe in [self._open_repo_v3, self._open_repo_v2,
 
379
            self._open_repo_v1]:
 
380
            try:
 
381
                response, real_repo = probe(path)
 
382
                break
 
383
            except errors.UnknownSmartMethod:
 
384
                pass
 
385
        if response is None:
 
386
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
 
387
        if response[0] != 'ok':
 
388
            raise errors.UnexpectedSmartServerResponse(response)
 
389
        if len(response) != 6:
 
390
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
391
        if response[1] == '':
 
392
            # repo is at this dir.
 
393
            format = response_tuple_to_repo_format(response[2:])
 
394
            # Used to support creating a real format instance when needed.
 
395
            format._creating_bzrdir = self
 
396
            remote_repo = RemoteRepository(self, format)
 
397
            format._creating_repo = remote_repo
 
398
            if real_repo is not None:
 
399
                remote_repo._set_real_repository(real_repo)
 
400
            return remote_repo
 
401
        else:
 
402
            raise errors.NoRepositoryPresent(self)
 
403
 
 
404
    def has_workingtree(self):
 
405
        if self._has_working_tree is None:
 
406
            self._ensure_real()
 
407
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
408
        return self._has_working_tree
 
409
 
 
410
    def open_workingtree(self, recommend_upgrade=True):
 
411
        if self.has_workingtree():
 
412
            raise errors.NotLocalUrl(self.root_transport)
 
413
        else:
 
414
            raise errors.NoWorkingTree(self.root_transport.base)
 
415
 
 
416
    def _path_for_remote_call(self, client):
 
417
        """Return the path to be used for this bzrdir in a remote call."""
 
418
        return client.remote_path_from_transport(self.root_transport)
 
419
 
 
420
    def get_branch_transport(self, branch_format):
 
421
        self._ensure_real()
 
422
        return self._real_bzrdir.get_branch_transport(branch_format)
 
423
 
 
424
    def get_repository_transport(self, repository_format):
 
425
        self._ensure_real()
 
426
        return self._real_bzrdir.get_repository_transport(repository_format)
 
427
 
 
428
    def get_workingtree_transport(self, workingtree_format):
 
429
        self._ensure_real()
 
430
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
431
 
 
432
    def can_convert_format(self):
 
433
        """Upgrading of remote bzrdirs is not supported yet."""
 
434
        return False
 
435
 
 
436
    def needs_format_conversion(self, format=None):
 
437
        """Upgrading of remote bzrdirs is not supported yet."""
 
438
        if format is None:
 
439
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
440
                % 'needs_format_conversion(format=None)')
 
441
        return False
 
442
 
 
443
    def clone(self, url, revision_id=None, force_new_repo=False,
 
444
              preserve_stacking=False):
 
445
        self._ensure_real()
 
446
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
447
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
448
 
 
449
    def _get_config(self):
 
450
        return RemoteBzrDirConfig(self)
 
451
 
 
452
 
 
453
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
454
    """Format for repositories accessed over a _SmartClient.
 
455
 
 
456
    Instances of this repository are represented by RemoteRepository
 
457
    instances.
 
458
 
 
459
    The RemoteRepositoryFormat is parameterized during construction
 
460
    to reflect the capabilities of the real, remote format. Specifically
 
461
    the attributes rich_root_data and supports_tree_reference are set
 
462
    on a per instance basis, and are not set (and should not be) at
 
463
    the class level.
 
464
 
 
465
    :ivar _custom_format: If set, a specific concrete repository format that
 
466
        will be used when initializing a repository with this
 
467
        RemoteRepositoryFormat.
 
468
    :ivar _creating_repo: If set, the repository object that this
 
469
        RemoteRepositoryFormat was created for: it can be called into
 
470
        to obtain data like the network name.
 
471
    """
 
472
 
 
473
    _matchingbzrdir = RemoteBzrDirFormat()
 
474
 
 
475
    def __init__(self):
 
476
        repository.RepositoryFormat.__init__(self)
 
477
        self._custom_format = None
 
478
        self._network_name = None
 
479
        self._creating_bzrdir = None
 
480
        self._supports_chks = None
 
481
        self._supports_external_lookups = None
 
482
        self._supports_tree_reference = None
 
483
        self._rich_root_data = None
 
484
 
 
485
    def __repr__(self):
 
486
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
487
            self._network_name)
 
488
 
 
489
    @property
 
490
    def fast_deltas(self):
 
491
        self._ensure_real()
 
492
        return self._custom_format.fast_deltas
 
493
 
 
494
    @property
 
495
    def rich_root_data(self):
 
496
        if self._rich_root_data is None:
 
497
            self._ensure_real()
 
498
            self._rich_root_data = self._custom_format.rich_root_data
 
499
        return self._rich_root_data
 
500
 
 
501
    @property
 
502
    def supports_chks(self):
 
503
        if self._supports_chks is None:
 
504
            self._ensure_real()
 
505
            self._supports_chks = self._custom_format.supports_chks
 
506
        return self._supports_chks
 
507
 
 
508
    @property
 
509
    def supports_external_lookups(self):
 
510
        if self._supports_external_lookups is None:
 
511
            self._ensure_real()
 
512
            self._supports_external_lookups = \
 
513
                self._custom_format.supports_external_lookups
 
514
        return self._supports_external_lookups
 
515
 
 
516
    @property
 
517
    def supports_tree_reference(self):
 
518
        if self._supports_tree_reference is None:
 
519
            self._ensure_real()
 
520
            self._supports_tree_reference = \
 
521
                self._custom_format.supports_tree_reference
 
522
        return self._supports_tree_reference
 
523
 
 
524
    def _vfs_initialize(self, a_bzrdir, shared):
 
525
        """Helper for common code in initialize."""
 
526
        if self._custom_format:
 
527
            # Custom format requested
 
528
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
529
        elif self._creating_bzrdir is not None:
 
530
            # Use the format that the repository we were created to back
 
531
            # has.
 
532
            prior_repo = self._creating_bzrdir.open_repository()
 
533
            prior_repo._ensure_real()
 
534
            result = prior_repo._real_repository._format.initialize(
 
535
                a_bzrdir, shared=shared)
 
536
        else:
 
537
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
 
538
            # support remote initialization.
 
539
            # We delegate to a real object at this point (as RemoteBzrDir
 
540
            # delegate to the repository format which would lead to infinite
 
541
            # recursion if we just called a_bzrdir.create_repository.
 
542
            a_bzrdir._ensure_real()
 
543
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
544
        if not isinstance(result, RemoteRepository):
 
545
            return self.open(a_bzrdir)
 
546
        else:
 
547
            return result
 
548
 
 
549
    def initialize(self, a_bzrdir, shared=False):
 
550
        # Being asked to create on a non RemoteBzrDir:
 
551
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
552
            return self._vfs_initialize(a_bzrdir, shared)
 
553
        medium = a_bzrdir._client._medium
 
554
        if medium._is_remote_before((1, 13)):
 
555
            return self._vfs_initialize(a_bzrdir, shared)
 
556
        # Creating on a remote bzr dir.
 
557
        # 1) get the network name to use.
 
558
        if self._custom_format:
 
559
            network_name = self._custom_format.network_name()
 
560
        elif self._network_name:
 
561
            network_name = self._network_name
 
562
        else:
 
563
            # Select the current bzrlib default and ask for that.
 
564
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
565
            reference_format = reference_bzrdir_format.repository_format
 
566
            network_name = reference_format.network_name()
 
567
        # 2) try direct creation via RPC
 
568
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
569
        verb = 'BzrDir.create_repository'
 
570
        if shared:
 
571
            shared_str = 'True'
 
572
        else:
 
573
            shared_str = 'False'
 
574
        try:
 
575
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
576
        except errors.UnknownSmartMethod:
 
577
            # Fallback - use vfs methods
 
578
            medium._remember_remote_is_before((1, 13))
 
579
            return self._vfs_initialize(a_bzrdir, shared)
 
580
        else:
 
581
            # Turn the response into a RemoteRepository object.
 
582
            format = response_tuple_to_repo_format(response[1:])
 
583
            # Used to support creating a real format instance when needed.
 
584
            format._creating_bzrdir = a_bzrdir
 
585
            remote_repo = RemoteRepository(a_bzrdir, format)
 
586
            format._creating_repo = remote_repo
 
587
            return remote_repo
 
588
 
 
589
    def open(self, a_bzrdir):
 
590
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
591
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
592
        return a_bzrdir.open_repository()
 
593
 
 
594
    def _ensure_real(self):
 
595
        if self._custom_format is None:
 
596
            self._custom_format = repository.network_format_registry.get(
 
597
                self._network_name)
 
598
 
 
599
    @property
 
600
    def _fetch_order(self):
 
601
        self._ensure_real()
 
602
        return self._custom_format._fetch_order
 
603
 
 
604
    @property
 
605
    def _fetch_uses_deltas(self):
 
606
        self._ensure_real()
 
607
        return self._custom_format._fetch_uses_deltas
 
608
 
 
609
    @property
 
610
    def _fetch_reconcile(self):
 
611
        self._ensure_real()
 
612
        return self._custom_format._fetch_reconcile
 
613
 
 
614
    def get_format_description(self):
 
615
        self._ensure_real()
 
616
        return 'Remote: ' + self._custom_format.get_format_description()
 
617
 
 
618
    def __eq__(self, other):
 
619
        return self.__class__ is other.__class__
 
620
 
 
621
    def network_name(self):
 
622
        if self._network_name:
 
623
            return self._network_name
 
624
        self._creating_repo._ensure_real()
 
625
        return self._creating_repo._real_repository._format.network_name()
 
626
 
 
627
    @property
 
628
    def pack_compresses(self):
 
629
        self._ensure_real()
 
630
        return self._custom_format.pack_compresses
 
631
 
 
632
    @property
 
633
    def _serializer(self):
 
634
        self._ensure_real()
 
635
        return self._custom_format._serializer
 
636
 
 
637
 
 
638
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
 
639
    """Repository accessed over rpc.
 
640
 
 
641
    For the moment most operations are performed using local transport-backed
 
642
    Repository objects.
 
643
    """
 
644
 
 
645
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
646
        """Create a RemoteRepository instance.
 
647
 
 
648
        :param remote_bzrdir: The bzrdir hosting this repository.
 
649
        :param format: The RemoteFormat object to use.
 
650
        :param real_repository: If not None, a local implementation of the
 
651
            repository logic for the repository, usually accessing the data
 
652
            via the VFS.
 
653
        :param _client: Private testing parameter - override the smart client
 
654
            to be used by the repository.
 
655
        """
 
656
        if real_repository:
 
657
            self._real_repository = real_repository
 
658
        else:
 
659
            self._real_repository = None
 
660
        self.bzrdir = remote_bzrdir
 
661
        if _client is None:
 
662
            self._client = remote_bzrdir._client
 
663
        else:
 
664
            self._client = _client
 
665
        self._format = format
 
666
        self._lock_mode = None
 
667
        self._lock_token = None
 
668
        self._lock_count = 0
 
669
        self._leave_lock = False
 
670
        # Cache of revision parents; misses are cached during read locks, and
 
671
        # write locks when no _real_repository has been set.
 
672
        self._unstacked_provider = graph.CachingParentsProvider(
 
673
            get_parent_map=self._get_parent_map_rpc)
 
674
        self._unstacked_provider.disable_cache()
 
675
        # For tests:
 
676
        # These depend on the actual remote format, so force them off for
 
677
        # maximum compatibility. XXX: In future these should depend on the
 
678
        # remote repository instance, but this is irrelevant until we perform
 
679
        # reconcile via an RPC call.
 
680
        self._reconcile_does_inventory_gc = False
 
681
        self._reconcile_fixes_text_parents = False
 
682
        self._reconcile_backsup_inventory = False
 
683
        self.base = self.bzrdir.transport.base
 
684
        # Additional places to query for data.
 
685
        self._fallback_repositories = []
 
686
 
 
687
    def __str__(self):
 
688
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
689
 
 
690
    __repr__ = __str__
 
691
 
 
692
    def abort_write_group(self, suppress_errors=False):
 
693
        """Complete a write group on the decorated repository.
 
694
 
 
695
        Smart methods perform operations in a single step so this API
 
696
        is not really applicable except as a compatibility thunk
 
697
        for older plugins that don't use e.g. the CommitBuilder
 
698
        facility.
 
699
 
 
700
        :param suppress_errors: see Repository.abort_write_group.
 
701
        """
 
702
        self._ensure_real()
 
703
        return self._real_repository.abort_write_group(
 
704
            suppress_errors=suppress_errors)
 
705
 
 
706
    @property
 
707
    def chk_bytes(self):
 
708
        """Decorate the real repository for now.
 
709
 
 
710
        In the long term a full blown network facility is needed to avoid
 
711
        creating a real repository object locally.
 
712
        """
 
713
        self._ensure_real()
 
714
        return self._real_repository.chk_bytes
 
715
 
 
716
    def commit_write_group(self):
 
717
        """Complete a write group on the decorated repository.
 
718
 
 
719
        Smart methods perform operations in a single step so this API
 
720
        is not really applicable except as a compatibility thunk
 
721
        for older plugins that don't use e.g. the CommitBuilder
 
722
        facility.
 
723
        """
 
724
        self._ensure_real()
 
725
        return self._real_repository.commit_write_group()
 
726
 
 
727
    def resume_write_group(self, tokens):
 
728
        self._ensure_real()
 
729
        return self._real_repository.resume_write_group(tokens)
 
730
 
 
731
    def suspend_write_group(self):
 
732
        self._ensure_real()
 
733
        return self._real_repository.suspend_write_group()
 
734
 
 
735
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
736
        self._ensure_real()
 
737
        return self._real_repository.get_missing_parent_inventories(
 
738
            check_for_missing_texts=check_for_missing_texts)
 
739
 
 
740
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
 
741
        self._ensure_real()
 
742
        return self._real_repository.get_rev_id_for_revno(
 
743
            revno, known_pair)
 
744
 
 
745
    def get_rev_id_for_revno(self, revno, known_pair):
 
746
        """See Repository.get_rev_id_for_revno."""
 
747
        path = self.bzrdir._path_for_remote_call(self._client)
 
748
        try:
 
749
            if self._client._medium._is_remote_before((1, 17)):
 
750
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
751
            response = self._call(
 
752
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
753
        except errors.UnknownSmartMethod:
 
754
            self._client._medium._remember_remote_is_before((1, 17))
 
755
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
756
        if response[0] == 'ok':
 
757
            return True, response[1]
 
758
        elif response[0] == 'history-incomplete':
 
759
            known_pair = response[1:3]
 
760
            for fallback in self._fallback_repositories:
 
761
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
 
762
                if found:
 
763
                    return True, result
 
764
                else:
 
765
                    known_pair = result
 
766
            # Not found in any fallbacks
 
767
            return False, known_pair
 
768
        else:
 
769
            raise errors.UnexpectedSmartServerResponse(response)
 
770
 
 
771
    def _ensure_real(self):
 
772
        """Ensure that there is a _real_repository set.
 
773
 
 
774
        Used before calls to self._real_repository.
 
775
 
 
776
        Note that _ensure_real causes many roundtrips to the server which are
 
777
        not desirable, and prevents the use of smart one-roundtrip RPC's to
 
778
        perform complex operations (such as accessing parent data, streaming
 
779
        revisions etc). Adding calls to _ensure_real should only be done when
 
780
        bringing up new functionality, adding fallbacks for smart methods that
 
781
        require a fallback path, and never to replace an existing smart method
 
782
        invocation. If in doubt chat to the bzr network team.
 
783
        """
 
784
        if self._real_repository is None:
 
785
            if 'hpssvfs' in debug.debug_flags:
 
786
                import traceback
 
787
                warning('VFS Repository access triggered\n%s',
 
788
                    ''.join(traceback.format_stack()))
 
789
            self._unstacked_provider.missing_keys.clear()
 
790
            self.bzrdir._ensure_real()
 
791
            self._set_real_repository(
 
792
                self.bzrdir._real_bzrdir.open_repository())
 
793
 
 
794
    def _translate_error(self, err, **context):
 
795
        self.bzrdir._translate_error(err, repository=self, **context)
 
796
 
 
797
    def find_text_key_references(self):
 
798
        """Find the text key references within the repository.
 
799
 
 
800
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
801
        revision_ids. Each altered file-ids has the exact revision_ids that
 
802
        altered it listed explicitly.
 
803
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
804
            to whether they were referred to by the inventory of the
 
805
            revision_id that they contain. The inventory texts from all present
 
806
            revision ids are assessed to generate this report.
 
807
        """
 
808
        self._ensure_real()
 
809
        return self._real_repository.find_text_key_references()
 
810
 
 
811
    def _generate_text_key_index(self):
 
812
        """Generate a new text key index for the repository.
 
813
 
 
814
        This is an expensive function that will take considerable time to run.
 
815
 
 
816
        :return: A dict mapping (file_id, revision_id) tuples to a list of
 
817
            parents, also (file_id, revision_id) tuples.
 
818
        """
 
819
        self._ensure_real()
 
820
        return self._real_repository._generate_text_key_index()
 
821
 
 
822
    def _get_revision_graph(self, revision_id):
 
823
        """Private method for using with old (< 1.2) servers to fallback."""
 
824
        if revision_id is None:
 
825
            revision_id = ''
 
826
        elif revision.is_null(revision_id):
 
827
            return {}
 
828
 
 
829
        path = self.bzrdir._path_for_remote_call(self._client)
 
830
        response = self._call_expecting_body(
 
831
            'Repository.get_revision_graph', path, revision_id)
 
832
        response_tuple, response_handler = response
 
833
        if response_tuple[0] != 'ok':
 
834
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
835
        coded = response_handler.read_body_bytes()
 
836
        if coded == '':
 
837
            # no revisions in this repository!
 
838
            return {}
 
839
        lines = coded.split('\n')
 
840
        revision_graph = {}
 
841
        for line in lines:
 
842
            d = tuple(line.split())
 
843
            revision_graph[d[0]] = d[1:]
 
844
 
 
845
        return revision_graph
 
846
 
 
847
    def _get_sink(self):
 
848
        """See Repository._get_sink()."""
 
849
        return RemoteStreamSink(self)
 
850
 
 
851
    def _get_source(self, to_format):
 
852
        """Return a source for streaming from this repository."""
 
853
        return RemoteStreamSource(self, to_format)
 
854
 
 
855
    @needs_read_lock
 
856
    def has_revision(self, revision_id):
 
857
        """True if this repository has a copy of the revision."""
 
858
        # Copy of bzrlib.repository.Repository.has_revision
 
859
        return revision_id in self.has_revisions((revision_id,))
 
860
 
 
861
    @needs_read_lock
 
862
    def has_revisions(self, revision_ids):
 
863
        """Probe to find out the presence of multiple revisions.
 
864
 
 
865
        :param revision_ids: An iterable of revision_ids.
 
866
        :return: A set of the revision_ids that were present.
 
867
        """
 
868
        # Copy of bzrlib.repository.Repository.has_revisions
 
869
        parent_map = self.get_parent_map(revision_ids)
 
870
        result = set(parent_map)
 
871
        if _mod_revision.NULL_REVISION in revision_ids:
 
872
            result.add(_mod_revision.NULL_REVISION)
 
873
        return result
 
874
 
 
875
    def _has_same_fallbacks(self, other_repo):
 
876
        """Returns true if the repositories have the same fallbacks."""
 
877
        # XXX: copied from Repository; it should be unified into a base class
 
878
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
879
        my_fb = self._fallback_repositories
 
880
        other_fb = other_repo._fallback_repositories
 
881
        if len(my_fb) != len(other_fb):
 
882
            return False
 
883
        for f, g in zip(my_fb, other_fb):
 
884
            if not f.has_same_location(g):
 
885
                return False
 
886
        return True
 
887
 
 
888
    def has_same_location(self, other):
 
889
        # TODO: Move to RepositoryBase and unify with the regular Repository
 
890
        # one; unfortunately the tests rely on slightly different behaviour at
 
891
        # present -- mbp 20090710
 
892
        return (self.__class__ is other.__class__ and
 
893
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
894
 
 
895
    def get_graph(self, other_repository=None):
 
896
        """Return the graph for this repository format"""
 
897
        parents_provider = self._make_parents_provider(other_repository)
 
898
        return graph.Graph(parents_provider)
 
899
 
 
900
    def gather_stats(self, revid=None, committers=None):
 
901
        """See Repository.gather_stats()."""
 
902
        path = self.bzrdir._path_for_remote_call(self._client)
 
903
        # revid can be None to indicate no revisions, not just NULL_REVISION
 
904
        if revid is None or revision.is_null(revid):
 
905
            fmt_revid = ''
 
906
        else:
 
907
            fmt_revid = revid
 
908
        if committers is None or not committers:
 
909
            fmt_committers = 'no'
 
910
        else:
 
911
            fmt_committers = 'yes'
 
912
        response_tuple, response_handler = self._call_expecting_body(
 
913
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
914
        if response_tuple[0] != 'ok':
 
915
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
916
 
 
917
        body = response_handler.read_body_bytes()
 
918
        result = {}
 
919
        for line in body.split('\n'):
 
920
            if not line:
 
921
                continue
 
922
            key, val_text = line.split(':')
 
923
            if key in ('revisions', 'size', 'committers'):
 
924
                result[key] = int(val_text)
 
925
            elif key in ('firstrev', 'latestrev'):
 
926
                values = val_text.split(' ')[1:]
 
927
                result[key] = (float(values[0]), long(values[1]))
 
928
 
 
929
        return result
 
930
 
 
931
    def find_branches(self, using=False):
 
932
        """See Repository.find_branches()."""
 
933
        # should be an API call to the server.
 
934
        self._ensure_real()
 
935
        return self._real_repository.find_branches(using=using)
 
936
 
 
937
    def get_physical_lock_status(self):
 
938
        """See Repository.get_physical_lock_status()."""
 
939
        # should be an API call to the server.
 
940
        self._ensure_real()
 
941
        return self._real_repository.get_physical_lock_status()
 
942
 
 
943
    def is_in_write_group(self):
 
944
        """Return True if there is an open write group.
 
945
 
 
946
        write groups are only applicable locally for the smart server..
 
947
        """
 
948
        if self._real_repository:
 
949
            return self._real_repository.is_in_write_group()
 
950
 
 
951
    def is_locked(self):
 
952
        return self._lock_count >= 1
 
953
 
 
954
    def is_shared(self):
 
955
        """See Repository.is_shared()."""
 
956
        path = self.bzrdir._path_for_remote_call(self._client)
 
957
        response = self._call('Repository.is_shared', path)
 
958
        if response[0] not in ('yes', 'no'):
 
959
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
960
        return response[0] == 'yes'
 
961
 
 
962
    def is_write_locked(self):
 
963
        return self._lock_mode == 'w'
 
964
 
 
965
    def _warn_if_deprecated(self, branch=None):
 
966
        # If we have a real repository, the check will be done there, if we
 
967
        # don't the check will be done remotely.
 
968
        pass
 
969
 
 
970
    def lock_read(self):
 
971
        # wrong eventually - want a local lock cache context
 
972
        if not self._lock_mode:
 
973
            self._note_lock('r')
 
974
            self._lock_mode = 'r'
 
975
            self._lock_count = 1
 
976
            self._unstacked_provider.enable_cache(cache_misses=True)
 
977
            if self._real_repository is not None:
 
978
                self._real_repository.lock_read()
 
979
            for repo in self._fallback_repositories:
 
980
                repo.lock_read()
 
981
        else:
 
982
            self._lock_count += 1
 
983
 
 
984
    def _remote_lock_write(self, token):
 
985
        path = self.bzrdir._path_for_remote_call(self._client)
 
986
        if token is None:
 
987
            token = ''
 
988
        err_context = {'token': token}
 
989
        response = self._call('Repository.lock_write', path, token,
 
990
                              **err_context)
 
991
        if response[0] == 'ok':
 
992
            ok, token = response
 
993
            return token
 
994
        else:
 
995
            raise errors.UnexpectedSmartServerResponse(response)
 
996
 
 
997
    def lock_write(self, token=None, _skip_rpc=False):
 
998
        if not self._lock_mode:
 
999
            self._note_lock('w')
 
1000
            if _skip_rpc:
 
1001
                if self._lock_token is not None:
 
1002
                    if token != self._lock_token:
 
1003
                        raise errors.TokenMismatch(token, self._lock_token)
 
1004
                self._lock_token = token
 
1005
            else:
 
1006
                self._lock_token = self._remote_lock_write(token)
 
1007
            # if self._lock_token is None, then this is something like packs or
 
1008
            # svn where we don't get to lock the repo, or a weave style repository
 
1009
            # where we cannot lock it over the wire and attempts to do so will
 
1010
            # fail.
 
1011
            if self._real_repository is not None:
 
1012
                self._real_repository.lock_write(token=self._lock_token)
 
1013
            if token is not None:
 
1014
                self._leave_lock = True
 
1015
            else:
 
1016
                self._leave_lock = False
 
1017
            self._lock_mode = 'w'
 
1018
            self._lock_count = 1
 
1019
            cache_misses = self._real_repository is None
 
1020
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
 
1021
            for repo in self._fallback_repositories:
 
1022
                # Writes don't affect fallback repos
 
1023
                repo.lock_read()
 
1024
        elif self._lock_mode == 'r':
 
1025
            raise errors.ReadOnlyError(self)
 
1026
        else:
 
1027
            self._lock_count += 1
 
1028
        return self._lock_token or None
 
1029
 
 
1030
    def leave_lock_in_place(self):
 
1031
        if not self._lock_token:
 
1032
            raise NotImplementedError(self.leave_lock_in_place)
 
1033
        self._leave_lock = True
 
1034
 
 
1035
    def dont_leave_lock_in_place(self):
 
1036
        if not self._lock_token:
 
1037
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1038
        self._leave_lock = False
 
1039
 
 
1040
    def _set_real_repository(self, repository):
 
1041
        """Set the _real_repository for this repository.
 
1042
 
 
1043
        :param repository: The repository to fallback to for non-hpss
 
1044
            implemented operations.
 
1045
        """
 
1046
        if self._real_repository is not None:
 
1047
            # Replacing an already set real repository.
 
1048
            # We cannot do this [currently] if the repository is locked -
 
1049
            # synchronised state might be lost.
 
1050
            if self.is_locked():
 
1051
                raise AssertionError('_real_repository is already set')
 
1052
        if isinstance(repository, RemoteRepository):
 
1053
            raise AssertionError()
 
1054
        self._real_repository = repository
 
1055
        # three code paths happen here:
 
1056
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
 
1057
        # up stacking. In this case self._fallback_repositories is [], and the
 
1058
        # real repo is already setup. Preserve the real repo and
 
1059
        # RemoteRepository.add_fallback_repository will avoid adding
 
1060
        # duplicates.
 
1061
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
 
1062
        # ensure_real is triggered from a branch, the real repository to
 
1063
        # set already has a matching list with separate instances, but
 
1064
        # as they are also RemoteRepositories we don't worry about making the
 
1065
        # lists be identical.
 
1066
        # 3) new servers, RemoteRepository.ensure_real is triggered before
 
1067
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
 
1068
        # and need to populate it.
 
1069
        if (self._fallback_repositories and
 
1070
            len(self._real_repository._fallback_repositories) !=
 
1071
            len(self._fallback_repositories)):
 
1072
            if len(self._real_repository._fallback_repositories):
 
1073
                raise AssertionError(
 
1074
                    "cannot cleanly remove existing _fallback_repositories")
 
1075
        for fb in self._fallback_repositories:
 
1076
            self._real_repository.add_fallback_repository(fb)
 
1077
        if self._lock_mode == 'w':
 
1078
            # if we are already locked, the real repository must be able to
 
1079
            # acquire the lock with our token.
 
1080
            self._real_repository.lock_write(self._lock_token)
 
1081
        elif self._lock_mode == 'r':
 
1082
            self._real_repository.lock_read()
 
1083
 
 
1084
    def start_write_group(self):
 
1085
        """Start a write group on the decorated repository.
 
1086
 
 
1087
        Smart methods perform operations in a single step so this API
 
1088
        is not really applicable except as a compatibility thunk
 
1089
        for older plugins that don't use e.g. the CommitBuilder
 
1090
        facility.
 
1091
        """
 
1092
        self._ensure_real()
 
1093
        return self._real_repository.start_write_group()
 
1094
 
 
1095
    def _unlock(self, token):
 
1096
        path = self.bzrdir._path_for_remote_call(self._client)
 
1097
        if not token:
 
1098
            # with no token the remote repository is not persistently locked.
 
1099
            return
 
1100
        err_context = {'token': token}
 
1101
        response = self._call('Repository.unlock', path, token,
 
1102
                              **err_context)
 
1103
        if response == ('ok',):
 
1104
            return
 
1105
        else:
 
1106
            raise errors.UnexpectedSmartServerResponse(response)
 
1107
 
 
1108
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
1109
    def unlock(self):
 
1110
        if not self._lock_count:
 
1111
            return lock.cant_unlock_not_held(self)
 
1112
        self._lock_count -= 1
 
1113
        if self._lock_count > 0:
 
1114
            return
 
1115
        self._unstacked_provider.disable_cache()
 
1116
        old_mode = self._lock_mode
 
1117
        self._lock_mode = None
 
1118
        try:
 
1119
            # The real repository is responsible at present for raising an
 
1120
            # exception if it's in an unfinished write group.  However, it
 
1121
            # normally will *not* actually remove the lock from disk - that's
 
1122
            # done by the server on receiving the Repository.unlock call.
 
1123
            # This is just to let the _real_repository stay up to date.
 
1124
            if self._real_repository is not None:
 
1125
                self._real_repository.unlock()
 
1126
        finally:
 
1127
            # The rpc-level lock should be released even if there was a
 
1128
            # problem releasing the vfs-based lock.
 
1129
            if old_mode == 'w':
 
1130
                # Only write-locked repositories need to make a remote method
 
1131
                # call to perform the unlock.
 
1132
                old_token = self._lock_token
 
1133
                self._lock_token = None
 
1134
                if not self._leave_lock:
 
1135
                    self._unlock(old_token)
 
1136
        # Fallbacks are always 'lock_read()' so we don't pay attention to
 
1137
        # self._leave_lock
 
1138
        for repo in self._fallback_repositories:
 
1139
            repo.unlock()
 
1140
 
 
1141
    def break_lock(self):
 
1142
        # should hand off to the network
 
1143
        self._ensure_real()
 
1144
        return self._real_repository.break_lock()
 
1145
 
 
1146
    def _get_tarball(self, compression):
 
1147
        """Return a TemporaryFile containing a repository tarball.
 
1148
 
 
1149
        Returns None if the server does not support sending tarballs.
 
1150
        """
 
1151
        import tempfile
 
1152
        path = self.bzrdir._path_for_remote_call(self._client)
 
1153
        try:
 
1154
            response, protocol = self._call_expecting_body(
 
1155
                'Repository.tarball', path, compression)
 
1156
        except errors.UnknownSmartMethod:
 
1157
            protocol.cancel_read_body()
 
1158
            return None
 
1159
        if response[0] == 'ok':
 
1160
            # Extract the tarball and return it
 
1161
            t = tempfile.NamedTemporaryFile()
 
1162
            # TODO: rpc layer should read directly into it...
 
1163
            t.write(protocol.read_body_bytes())
 
1164
            t.seek(0)
 
1165
            return t
 
1166
        raise errors.UnexpectedSmartServerResponse(response)
 
1167
 
 
1168
    def sprout(self, to_bzrdir, revision_id=None):
 
1169
        # TODO: Option to control what format is created?
 
1170
        self._ensure_real()
 
1171
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
1172
                                                             shared=False)
 
1173
        dest_repo.fetch(self, revision_id=revision_id)
 
1174
        return dest_repo
 
1175
 
 
1176
    ### These methods are just thin shims to the VFS object for now.
 
1177
 
 
1178
    def revision_tree(self, revision_id):
 
1179
        self._ensure_real()
 
1180
        return self._real_repository.revision_tree(revision_id)
 
1181
 
 
1182
    def get_serializer_format(self):
 
1183
        self._ensure_real()
 
1184
        return self._real_repository.get_serializer_format()
 
1185
 
 
1186
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1187
                           timezone=None, committer=None, revprops=None,
 
1188
                           revision_id=None):
 
1189
        # FIXME: It ought to be possible to call this without immediately
 
1190
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
1191
        self._ensure_real()
 
1192
        real_repo = self._real_repository
 
1193
        builder = real_repo.get_commit_builder(branch, parents,
 
1194
                config, timestamp=timestamp, timezone=timezone,
 
1195
                committer=committer, revprops=revprops, revision_id=revision_id)
 
1196
        return builder
 
1197
 
 
1198
    def add_fallback_repository(self, repository):
 
1199
        """Add a repository to use for looking up data not held locally.
 
1200
 
 
1201
        :param repository: A repository.
 
1202
        """
 
1203
        if not self._format.supports_external_lookups:
 
1204
            raise errors.UnstackableRepositoryFormat(
 
1205
                self._format.network_name(), self.base)
 
1206
        # We need to accumulate additional repositories here, to pass them in
 
1207
        # on various RPC's.
 
1208
        #
 
1209
        if self.is_locked():
 
1210
            # We will call fallback.unlock() when we transition to the unlocked
 
1211
            # state, so always add a lock here. If a caller passes us a locked
 
1212
            # repository, they are responsible for unlocking it later.
 
1213
            repository.lock_read()
 
1214
        self._fallback_repositories.append(repository)
 
1215
        # If self._real_repository was parameterised already (e.g. because a
 
1216
        # _real_branch had its get_stacked_on_url method called), then the
 
1217
        # repository to be added may already be in the _real_repositories list.
 
1218
        if self._real_repository is not None:
 
1219
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
 
1220
                self._real_repository._fallback_repositories]
 
1221
            if repository.bzrdir.root_transport.base not in fallback_locations:
 
1222
                self._real_repository.add_fallback_repository(repository)
 
1223
 
 
1224
    def add_inventory(self, revid, inv, parents):
 
1225
        self._ensure_real()
 
1226
        return self._real_repository.add_inventory(revid, inv, parents)
 
1227
 
 
1228
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
 
1229
                               parents):
 
1230
        self._ensure_real()
 
1231
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
 
1232
            delta, new_revision_id, parents)
 
1233
 
 
1234
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
1235
        self._ensure_real()
 
1236
        return self._real_repository.add_revision(
 
1237
            rev_id, rev, inv=inv, config=config)
 
1238
 
 
1239
    @needs_read_lock
 
1240
    def get_inventory(self, revision_id):
 
1241
        self._ensure_real()
 
1242
        return self._real_repository.get_inventory(revision_id)
 
1243
 
 
1244
    def iter_inventories(self, revision_ids, ordering=None):
 
1245
        self._ensure_real()
 
1246
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1247
 
 
1248
    @needs_read_lock
 
1249
    def get_revision(self, revision_id):
 
1250
        self._ensure_real()
 
1251
        return self._real_repository.get_revision(revision_id)
 
1252
 
 
1253
    def get_transaction(self):
 
1254
        self._ensure_real()
 
1255
        return self._real_repository.get_transaction()
 
1256
 
 
1257
    @needs_read_lock
 
1258
    def clone(self, a_bzrdir, revision_id=None):
 
1259
        self._ensure_real()
 
1260
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
1261
 
 
1262
    def make_working_trees(self):
 
1263
        """See Repository.make_working_trees"""
 
1264
        self._ensure_real()
 
1265
        return self._real_repository.make_working_trees()
 
1266
 
 
1267
    def refresh_data(self):
 
1268
        """Re-read any data needed to to synchronise with disk.
 
1269
 
 
1270
        This method is intended to be called after another repository instance
 
1271
        (such as one used by a smart server) has inserted data into the
 
1272
        repository. It may not be called during a write group, but may be
 
1273
        called at any other time.
 
1274
        """
 
1275
        if self.is_in_write_group():
 
1276
            raise errors.InternalBzrError(
 
1277
                "May not refresh_data while in a write group.")
 
1278
        if self._real_repository is not None:
 
1279
            self._real_repository.refresh_data()
 
1280
 
 
1281
    def revision_ids_to_search_result(self, result_set):
 
1282
        """Convert a set of revision ids to a graph SearchResult."""
 
1283
        result_parents = set()
 
1284
        for parents in self.get_graph().get_parent_map(
 
1285
            result_set).itervalues():
 
1286
            result_parents.update(parents)
 
1287
        included_keys = result_set.intersection(result_parents)
 
1288
        start_keys = result_set.difference(included_keys)
 
1289
        exclude_keys = result_parents.difference(result_set)
 
1290
        result = graph.SearchResult(start_keys, exclude_keys,
 
1291
            len(result_set), result_set)
 
1292
        return result
 
1293
 
 
1294
    @needs_read_lock
 
1295
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1296
        """Return the revision ids that other has that this does not.
 
1297
 
 
1298
        These are returned in topological order.
 
1299
 
 
1300
        revision_id: only return revision ids included by revision_id.
 
1301
        """
 
1302
        return repository.InterRepository.get(
 
1303
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
1304
 
 
1305
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1306
            fetch_spec=None):
 
1307
        # No base implementation to use as RemoteRepository is not a subclass
 
1308
        # of Repository; so this is a copy of Repository.fetch().
 
1309
        if fetch_spec is not None and revision_id is not None:
 
1310
            raise AssertionError(
 
1311
                "fetch_spec and revision_id are mutually exclusive.")
 
1312
        if self.is_in_write_group():
 
1313
            raise errors.InternalBzrError(
 
1314
                "May not fetch while in a write group.")
 
1315
        # fast path same-url fetch operations
 
1316
        if (self.has_same_location(source)
 
1317
            and fetch_spec is None
 
1318
            and self._has_same_fallbacks(source)):
 
1319
            # check that last_revision is in 'from' and then return a
 
1320
            # no-operation.
 
1321
            if (revision_id is not None and
 
1322
                not revision.is_null(revision_id)):
 
1323
                self.get_revision(revision_id)
 
1324
            return 0, []
 
1325
        # if there is no specific appropriate InterRepository, this will get
 
1326
        # the InterRepository base class, which raises an
 
1327
        # IncompatibleRepositories when asked to fetch.
 
1328
        inter = repository.InterRepository.get(source, self)
 
1329
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1330
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1331
 
 
1332
    def create_bundle(self, target, base, fileobj, format=None):
 
1333
        self._ensure_real()
 
1334
        self._real_repository.create_bundle(target, base, fileobj, format)
 
1335
 
 
1336
    @needs_read_lock
 
1337
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1338
        self._ensure_real()
 
1339
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
1340
 
 
1341
    def fileids_altered_by_revision_ids(self, revision_ids):
 
1342
        self._ensure_real()
 
1343
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
1344
 
 
1345
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
 
1346
        self._ensure_real()
 
1347
        return self._real_repository._get_versioned_file_checker(
 
1348
            revisions, revision_versions_cache)
 
1349
 
 
1350
    def iter_files_bytes(self, desired_files):
 
1351
        """See Repository.iter_file_bytes.
 
1352
        """
 
1353
        self._ensure_real()
 
1354
        return self._real_repository.iter_files_bytes(desired_files)
 
1355
 
 
1356
    def get_parent_map(self, revision_ids):
 
1357
        """See bzrlib.Graph.get_parent_map()."""
 
1358
        return self._make_parents_provider().get_parent_map(revision_ids)
 
1359
 
 
1360
    def _get_parent_map_rpc(self, keys):
 
1361
        """Helper for get_parent_map that performs the RPC."""
 
1362
        medium = self._client._medium
 
1363
        if medium._is_remote_before((1, 2)):
 
1364
            # We already found out that the server can't understand
 
1365
            # Repository.get_parent_map requests, so just fetch the whole
 
1366
            # graph.
 
1367
            #
 
1368
            # Note that this reads the whole graph, when only some keys are
 
1369
            # wanted.  On this old server there's no way (?) to get them all
 
1370
            # in one go, and the user probably will have seen a warning about
 
1371
            # the server being old anyhow.
 
1372
            rg = self._get_revision_graph(None)
 
1373
            # There is an API discrepancy between get_parent_map and
 
1374
            # get_revision_graph. Specifically, a "key:()" pair in
 
1375
            # get_revision_graph just means a node has no parents. For
 
1376
            # "get_parent_map" it means the node is a ghost. So fix up the
 
1377
            # graph to correct this.
 
1378
            #   https://bugs.launchpad.net/bzr/+bug/214894
 
1379
            # There is one other "bug" which is that ghosts in
 
1380
            # get_revision_graph() are not returned at all. But we won't worry
 
1381
            # about that for now.
 
1382
            for node_id, parent_ids in rg.iteritems():
 
1383
                if parent_ids == ():
 
1384
                    rg[node_id] = (NULL_REVISION,)
 
1385
            rg[NULL_REVISION] = ()
 
1386
            return rg
 
1387
 
 
1388
        keys = set(keys)
 
1389
        if None in keys:
 
1390
            raise ValueError('get_parent_map(None) is not valid')
 
1391
        if NULL_REVISION in keys:
 
1392
            keys.discard(NULL_REVISION)
 
1393
            found_parents = {NULL_REVISION:()}
 
1394
            if not keys:
 
1395
                return found_parents
 
1396
        else:
 
1397
            found_parents = {}
 
1398
        # TODO(Needs analysis): We could assume that the keys being requested
 
1399
        # from get_parent_map are in a breadth first search, so typically they
 
1400
        # will all be depth N from some common parent, and we don't have to
 
1401
        # have the server iterate from the root parent, but rather from the
 
1402
        # keys we're searching; and just tell the server the keyspace we
 
1403
        # already have; but this may be more traffic again.
 
1404
 
 
1405
        # Transform self._parents_map into a search request recipe.
 
1406
        # TODO: Manage this incrementally to avoid covering the same path
 
1407
        # repeatedly. (The server will have to on each request, but the less
 
1408
        # work done the better).
 
1409
        #
 
1410
        # Negative caching notes:
 
1411
        # new server sends missing when a request including the revid
 
1412
        # 'include-missing:' is present in the request.
 
1413
        # missing keys are serialised as missing:X, and we then call
 
1414
        # provider.note_missing(X) for-all X
 
1415
        parents_map = self._unstacked_provider.get_cached_map()
 
1416
        if parents_map is None:
 
1417
            # Repository is not locked, so there's no cache.
 
1418
            parents_map = {}
 
1419
        # start_set is all the keys in the cache
 
1420
        start_set = set(parents_map)
 
1421
        # result set is all the references to keys in the cache
 
1422
        result_parents = set()
 
1423
        for parents in parents_map.itervalues():
 
1424
            result_parents.update(parents)
 
1425
        stop_keys = result_parents.difference(start_set)
 
1426
        # We don't need to send ghosts back to the server as a position to
 
1427
        # stop either.
 
1428
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1429
        key_count = len(parents_map)
 
1430
        if (NULL_REVISION in result_parents
 
1431
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1432
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1433
            # in our cache of "missing" keys we need to increment our key count
 
1434
            # by 1, because the reconsitituted SearchResult on the server will
 
1435
            # still consider NULL_REVISION to be an included key.
 
1436
            key_count += 1
 
1437
        included_keys = start_set.intersection(result_parents)
 
1438
        start_set.difference_update(included_keys)
 
1439
        recipe = ('manual', start_set, stop_keys, key_count)
 
1440
        body = self._serialise_search_recipe(recipe)
 
1441
        path = self.bzrdir._path_for_remote_call(self._client)
 
1442
        for key in keys:
 
1443
            if type(key) is not str:
 
1444
                raise ValueError(
 
1445
                    "key %r not a plain string" % (key,))
 
1446
        verb = 'Repository.get_parent_map'
 
1447
        args = (path, 'include-missing:') + tuple(keys)
 
1448
        try:
 
1449
            response = self._call_with_body_bytes_expecting_body(
 
1450
                verb, args, body)
 
1451
        except errors.UnknownSmartMethod:
 
1452
            # Server does not support this method, so get the whole graph.
 
1453
            # Worse, we have to force a disconnection, because the server now
 
1454
            # doesn't realise it has a body on the wire to consume, so the
 
1455
            # only way to recover is to abandon the connection.
 
1456
            warning(
 
1457
                'Server is too old for fast get_parent_map, reconnecting.  '
 
1458
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
1459
            medium.disconnect()
 
1460
            # To avoid having to disconnect repeatedly, we keep track of the
 
1461
            # fact the server doesn't understand remote methods added in 1.2.
 
1462
            medium._remember_remote_is_before((1, 2))
 
1463
            # Recurse just once and we should use the fallback code.
 
1464
            return self._get_parent_map_rpc(keys)
 
1465
        response_tuple, response_handler = response
 
1466
        if response_tuple[0] not in ['ok']:
 
1467
            response_handler.cancel_read_body()
 
1468
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1469
        if response_tuple[0] == 'ok':
 
1470
            coded = bz2.decompress(response_handler.read_body_bytes())
 
1471
            if coded == '':
 
1472
                # no revisions found
 
1473
                return {}
 
1474
            lines = coded.split('\n')
 
1475
            revision_graph = {}
 
1476
            for line in lines:
 
1477
                d = tuple(line.split())
 
1478
                if len(d) > 1:
 
1479
                    revision_graph[d[0]] = d[1:]
 
1480
                else:
 
1481
                    # No parents:
 
1482
                    if d[0].startswith('missing:'):
 
1483
                        revid = d[0][8:]
 
1484
                        self._unstacked_provider.note_missing_key(revid)
 
1485
                    else:
 
1486
                        # no parents - so give the Graph result
 
1487
                        # (NULL_REVISION,).
 
1488
                        revision_graph[d[0]] = (NULL_REVISION,)
 
1489
            return revision_graph
 
1490
 
 
1491
    @needs_read_lock
 
1492
    def get_signature_text(self, revision_id):
 
1493
        self._ensure_real()
 
1494
        return self._real_repository.get_signature_text(revision_id)
 
1495
 
 
1496
    @needs_read_lock
 
1497
    def get_inventory_xml(self, revision_id):
 
1498
        self._ensure_real()
 
1499
        return self._real_repository.get_inventory_xml(revision_id)
 
1500
 
 
1501
    def deserialise_inventory(self, revision_id, xml):
 
1502
        self._ensure_real()
 
1503
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
1504
 
 
1505
    def reconcile(self, other=None, thorough=False):
 
1506
        self._ensure_real()
 
1507
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
1508
 
 
1509
    def all_revision_ids(self):
 
1510
        self._ensure_real()
 
1511
        return self._real_repository.all_revision_ids()
 
1512
 
 
1513
    @needs_read_lock
 
1514
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1515
        self._ensure_real()
 
1516
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1517
            specific_fileids=specific_fileids)
 
1518
 
 
1519
    @needs_read_lock
 
1520
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1521
        self._ensure_real()
 
1522
        return self._real_repository.get_revision_delta(revision_id,
 
1523
            specific_fileids=specific_fileids)
 
1524
 
 
1525
    @needs_read_lock
 
1526
    def revision_trees(self, revision_ids):
 
1527
        self._ensure_real()
 
1528
        return self._real_repository.revision_trees(revision_ids)
 
1529
 
 
1530
    @needs_read_lock
 
1531
    def get_revision_reconcile(self, revision_id):
 
1532
        self._ensure_real()
 
1533
        return self._real_repository.get_revision_reconcile(revision_id)
 
1534
 
 
1535
    @needs_read_lock
 
1536
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
1537
        self._ensure_real()
 
1538
        return self._real_repository.check(revision_ids=revision_ids,
 
1539
            callback_refs=callback_refs, check_repo=check_repo)
 
1540
 
 
1541
    def copy_content_into(self, destination, revision_id=None):
 
1542
        self._ensure_real()
 
1543
        return self._real_repository.copy_content_into(
 
1544
            destination, revision_id=revision_id)
 
1545
 
 
1546
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
1547
        # get a tarball of the remote repository, and copy from that into the
 
1548
        # destination
 
1549
        from bzrlib import osutils
 
1550
        import tarfile
 
1551
        # TODO: Maybe a progress bar while streaming the tarball?
 
1552
        note("Copying repository content as tarball...")
 
1553
        tar_file = self._get_tarball('bz2')
 
1554
        if tar_file is None:
 
1555
            return None
 
1556
        destination = to_bzrdir.create_repository()
 
1557
        try:
 
1558
            tar = tarfile.open('repository', fileobj=tar_file,
 
1559
                mode='r|bz2')
 
1560
            tmpdir = osutils.mkdtemp()
 
1561
            try:
 
1562
                _extract_tar(tar, tmpdir)
 
1563
                tmp_bzrdir = BzrDir.open(tmpdir)
 
1564
                tmp_repo = tmp_bzrdir.open_repository()
 
1565
                tmp_repo.copy_content_into(destination, revision_id)
 
1566
            finally:
 
1567
                osutils.rmtree(tmpdir)
 
1568
        finally:
 
1569
            tar_file.close()
 
1570
        return destination
 
1571
        # TODO: Suggestion from john: using external tar is much faster than
 
1572
        # python's tarfile library, but it may not work on windows.
 
1573
 
 
1574
    @property
 
1575
    def inventories(self):
 
1576
        """Decorate the real repository for now.
 
1577
 
 
1578
        In the long term a full blown network facility is needed to
 
1579
        avoid creating a real repository object locally.
 
1580
        """
 
1581
        self._ensure_real()
 
1582
        return self._real_repository.inventories
 
1583
 
 
1584
    @needs_write_lock
 
1585
    def pack(self, hint=None):
 
1586
        """Compress the data within the repository.
 
1587
 
 
1588
        This is not currently implemented within the smart server.
 
1589
        """
 
1590
        self._ensure_real()
 
1591
        return self._real_repository.pack(hint=hint)
 
1592
 
 
1593
    @property
 
1594
    def revisions(self):
 
1595
        """Decorate the real repository for now.
 
1596
 
 
1597
        In the short term this should become a real object to intercept graph
 
1598
        lookups.
 
1599
 
 
1600
        In the long term a full blown network facility is needed.
 
1601
        """
 
1602
        self._ensure_real()
 
1603
        return self._real_repository.revisions
 
1604
 
 
1605
    def set_make_working_trees(self, new_value):
 
1606
        if new_value:
 
1607
            new_value_str = "True"
 
1608
        else:
 
1609
            new_value_str = "False"
 
1610
        path = self.bzrdir._path_for_remote_call(self._client)
 
1611
        try:
 
1612
            response = self._call(
 
1613
                'Repository.set_make_working_trees', path, new_value_str)
 
1614
        except errors.UnknownSmartMethod:
 
1615
            self._ensure_real()
 
1616
            self._real_repository.set_make_working_trees(new_value)
 
1617
        else:
 
1618
            if response[0] != 'ok':
 
1619
                raise errors.UnexpectedSmartServerResponse(response)
 
1620
 
 
1621
    @property
 
1622
    def signatures(self):
 
1623
        """Decorate the real repository for now.
 
1624
 
 
1625
        In the long term a full blown network facility is needed to avoid
 
1626
        creating a real repository object locally.
 
1627
        """
 
1628
        self._ensure_real()
 
1629
        return self._real_repository.signatures
 
1630
 
 
1631
    @needs_write_lock
 
1632
    def sign_revision(self, revision_id, gpg_strategy):
 
1633
        self._ensure_real()
 
1634
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
1635
 
 
1636
    @property
 
1637
    def texts(self):
 
1638
        """Decorate the real repository for now.
 
1639
 
 
1640
        In the long term a full blown network facility is needed to avoid
 
1641
        creating a real repository object locally.
 
1642
        """
 
1643
        self._ensure_real()
 
1644
        return self._real_repository.texts
 
1645
 
 
1646
    @needs_read_lock
 
1647
    def get_revisions(self, revision_ids):
 
1648
        self._ensure_real()
 
1649
        return self._real_repository.get_revisions(revision_ids)
 
1650
 
 
1651
    def supports_rich_root(self):
 
1652
        return self._format.rich_root_data
 
1653
 
 
1654
    def iter_reverse_revision_history(self, revision_id):
 
1655
        self._ensure_real()
 
1656
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
1657
 
 
1658
    @property
 
1659
    def _serializer(self):
 
1660
        return self._format._serializer
 
1661
 
 
1662
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1663
        self._ensure_real()
 
1664
        return self._real_repository.store_revision_signature(
 
1665
            gpg_strategy, plaintext, revision_id)
 
1666
 
 
1667
    def add_signature_text(self, revision_id, signature):
 
1668
        self._ensure_real()
 
1669
        return self._real_repository.add_signature_text(revision_id, signature)
 
1670
 
 
1671
    def has_signature_for_revision_id(self, revision_id):
 
1672
        self._ensure_real()
 
1673
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
1674
 
 
1675
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1676
        self._ensure_real()
 
1677
        return self._real_repository.item_keys_introduced_by(revision_ids,
 
1678
            _files_pb=_files_pb)
 
1679
 
 
1680
    def revision_graph_can_have_wrong_parents(self):
 
1681
        # The answer depends on the remote repo format.
 
1682
        self._ensure_real()
 
1683
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
1684
 
 
1685
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
 
1686
        self._ensure_real()
 
1687
        return self._real_repository._find_inconsistent_revision_parents(
 
1688
            revisions_iterator)
 
1689
 
 
1690
    def _check_for_inconsistent_revision_parents(self):
 
1691
        self._ensure_real()
 
1692
        return self._real_repository._check_for_inconsistent_revision_parents()
 
1693
 
 
1694
    def _make_parents_provider(self, other=None):
 
1695
        providers = [self._unstacked_provider]
 
1696
        if other is not None:
 
1697
            providers.insert(0, other)
 
1698
        providers.extend(r._make_parents_provider() for r in
 
1699
                         self._fallback_repositories)
 
1700
        return graph.StackedParentsProvider(providers)
 
1701
 
 
1702
    def _serialise_search_recipe(self, recipe):
 
1703
        """Serialise a graph search recipe.
 
1704
 
 
1705
        :param recipe: A search recipe (start, stop, count).
 
1706
        :return: Serialised bytes.
 
1707
        """
 
1708
        start_keys = ' '.join(recipe[1])
 
1709
        stop_keys = ' '.join(recipe[2])
 
1710
        count = str(recipe[3])
 
1711
        return '\n'.join((start_keys, stop_keys, count))
 
1712
 
 
1713
    def _serialise_search_result(self, search_result):
 
1714
        if isinstance(search_result, graph.PendingAncestryResult):
 
1715
            parts = ['ancestry-of']
 
1716
            parts.extend(search_result.heads)
 
1717
        else:
 
1718
            recipe = search_result.get_recipe()
 
1719
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1720
        return '\n'.join(parts)
 
1721
 
 
1722
    def autopack(self):
 
1723
        path = self.bzrdir._path_for_remote_call(self._client)
 
1724
        try:
 
1725
            response = self._call('PackRepository.autopack', path)
 
1726
        except errors.UnknownSmartMethod:
 
1727
            self._ensure_real()
 
1728
            self._real_repository._pack_collection.autopack()
 
1729
            return
 
1730
        self.refresh_data()
 
1731
        if response[0] != 'ok':
 
1732
            raise errors.UnexpectedSmartServerResponse(response)
 
1733
 
 
1734
 
 
1735
class RemoteStreamSink(repository.StreamSink):
 
1736
 
 
1737
    def _insert_real(self, stream, src_format, resume_tokens):
 
1738
        self.target_repo._ensure_real()
 
1739
        sink = self.target_repo._real_repository._get_sink()
 
1740
        result = sink.insert_stream(stream, src_format, resume_tokens)
 
1741
        if not result:
 
1742
            self.target_repo.autopack()
 
1743
        return result
 
1744
 
 
1745
    def insert_stream(self, stream, src_format, resume_tokens):
 
1746
        target = self.target_repo
 
1747
        target._unstacked_provider.missing_keys.clear()
 
1748
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
 
1749
        if target._lock_token:
 
1750
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1751
            lock_args = (target._lock_token or '',)
 
1752
        else:
 
1753
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
1754
            lock_args = ()
 
1755
        client = target._client
 
1756
        medium = client._medium
 
1757
        path = target.bzrdir._path_for_remote_call(client)
 
1758
        # Probe for the verb to use with an empty stream before sending the
 
1759
        # real stream to it.  We do this both to avoid the risk of sending a
 
1760
        # large request that is then rejected, and because we don't want to
 
1761
        # implement a way to buffer, rewind, or restart the stream.
 
1762
        found_verb = False
 
1763
        for verb, required_version in candidate_calls:
 
1764
            if medium._is_remote_before(required_version):
 
1765
                continue
 
1766
            if resume_tokens:
 
1767
                # We've already done the probing (and set _is_remote_before) on
 
1768
                # a previous insert.
 
1769
                found_verb = True
 
1770
                break
 
1771
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
 
1772
            try:
 
1773
                response = client.call_with_body_stream(
 
1774
                    (verb, path, '') + lock_args, byte_stream)
 
1775
            except errors.UnknownSmartMethod:
 
1776
                medium._remember_remote_is_before(required_version)
 
1777
            else:
 
1778
                found_verb = True
 
1779
                break
 
1780
        if not found_verb:
 
1781
            # Have to use VFS.
 
1782
            return self._insert_real(stream, src_format, resume_tokens)
 
1783
        self._last_inv_record = None
 
1784
        self._last_substream = None
 
1785
        if required_version < (1, 19):
 
1786
            # Remote side doesn't support inventory deltas.  Wrap the stream to
 
1787
            # make sure we don't send any.  If the stream contains inventory
 
1788
            # deltas we'll interrupt the smart insert_stream request and
 
1789
            # fallback to VFS.
 
1790
            stream = self._stop_stream_if_inventory_delta(stream)
 
1791
        byte_stream = smart_repo._stream_to_byte_stream(
 
1792
            stream, src_format)
 
1793
        resume_tokens = ' '.join(resume_tokens)
 
1794
        response = client.call_with_body_stream(
 
1795
            (verb, path, resume_tokens) + lock_args, byte_stream)
 
1796
        if response[0][0] not in ('ok', 'missing-basis'):
 
1797
            raise errors.UnexpectedSmartServerResponse(response)
 
1798
        if self._last_substream is not None:
 
1799
            # The stream included an inventory-delta record, but the remote
 
1800
            # side isn't new enough to support them.  So we need to send the
 
1801
            # rest of the stream via VFS.
 
1802
            self.target_repo.refresh_data()
 
1803
            return self._resume_stream_with_vfs(response, src_format)
 
1804
        if response[0][0] == 'missing-basis':
 
1805
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1806
            resume_tokens = tokens
 
1807
            return resume_tokens, set(missing_keys)
 
1808
        else:
 
1809
            self.target_repo.refresh_data()
 
1810
            return [], set()
 
1811
 
 
1812
    def _resume_stream_with_vfs(self, response, src_format):
 
1813
        """Resume sending a stream via VFS, first resending the record and
 
1814
        substream that couldn't be sent via an insert_stream verb.
 
1815
        """
 
1816
        if response[0][0] == 'missing-basis':
 
1817
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1818
            # Ignore missing_keys, we haven't finished inserting yet
 
1819
        else:
 
1820
            tokens = []
 
1821
        def resume_substream():
 
1822
            # Yield the substream that was interrupted.
 
1823
            for record in self._last_substream:
 
1824
                yield record
 
1825
            self._last_substream = None
 
1826
        def resume_stream():
 
1827
            # Finish sending the interrupted substream
 
1828
            yield ('inventory-deltas', resume_substream())
 
1829
            # Then simply continue sending the rest of the stream.
 
1830
            for substream_kind, substream in self._last_stream:
 
1831
                yield substream_kind, substream
 
1832
        return self._insert_real(resume_stream(), src_format, tokens)
 
1833
 
 
1834
    def _stop_stream_if_inventory_delta(self, stream):
 
1835
        """Normally this just lets the original stream pass-through unchanged.
 
1836
 
 
1837
        However if any 'inventory-deltas' substream occurs it will stop
 
1838
        streaming, and store the interrupted substream and stream in
 
1839
        self._last_substream and self._last_stream so that the stream can be
 
1840
        resumed by _resume_stream_with_vfs.
 
1841
        """
 
1842
                    
 
1843
        stream_iter = iter(stream)
 
1844
        for substream_kind, substream in stream_iter:
 
1845
            if substream_kind == 'inventory-deltas':
 
1846
                self._last_substream = substream
 
1847
                self._last_stream = stream_iter
 
1848
                return
 
1849
            else:
 
1850
                yield substream_kind, substream
 
1851
            
 
1852
 
 
1853
class RemoteStreamSource(repository.StreamSource):
 
1854
    """Stream data from a remote server."""
 
1855
 
 
1856
    def get_stream(self, search):
 
1857
        if (self.from_repository._fallback_repositories and
 
1858
            self.to_format._fetch_order == 'topological'):
 
1859
            return self._real_stream(self.from_repository, search)
 
1860
        sources = []
 
1861
        seen = set()
 
1862
        repos = [self.from_repository]
 
1863
        while repos:
 
1864
            repo = repos.pop(0)
 
1865
            if repo in seen:
 
1866
                continue
 
1867
            seen.add(repo)
 
1868
            repos.extend(repo._fallback_repositories)
 
1869
            sources.append(repo)
 
1870
        return self.missing_parents_chain(search, sources)
 
1871
 
 
1872
    def get_stream_for_missing_keys(self, missing_keys):
 
1873
        self.from_repository._ensure_real()
 
1874
        real_repo = self.from_repository._real_repository
 
1875
        real_source = real_repo._get_source(self.to_format)
 
1876
        return real_source.get_stream_for_missing_keys(missing_keys)
 
1877
 
 
1878
    def _real_stream(self, repo, search):
 
1879
        """Get a stream for search from repo.
 
1880
        
 
1881
        This never called RemoteStreamSource.get_stream, and is a heler
 
1882
        for RemoteStreamSource._get_stream to allow getting a stream 
 
1883
        reliably whether fallback back because of old servers or trying
 
1884
        to stream from a non-RemoteRepository (which the stacked support
 
1885
        code will do).
 
1886
        """
 
1887
        source = repo._get_source(self.to_format)
 
1888
        if isinstance(source, RemoteStreamSource):
 
1889
            repo._ensure_real()
 
1890
            source = repo._real_repository._get_source(self.to_format)
 
1891
        return source.get_stream(search)
 
1892
 
 
1893
    def _get_stream(self, repo, search):
 
1894
        """Core worker to get a stream from repo for search.
 
1895
 
 
1896
        This is used by both get_stream and the stacking support logic. It
 
1897
        deliberately gets a stream for repo which does not need to be
 
1898
        self.from_repository. In the event that repo is not Remote, or
 
1899
        cannot do a smart stream, a fallback is made to the generic
 
1900
        repository._get_stream() interface, via self._real_stream.
 
1901
 
 
1902
        In the event of stacking, streams from _get_stream will not
 
1903
        contain all the data for search - this is normal (see get_stream).
 
1904
 
 
1905
        :param repo: A repository.
 
1906
        :param search: A search.
 
1907
        """
 
1908
        # Fallbacks may be non-smart
 
1909
        if not isinstance(repo, RemoteRepository):
 
1910
            return self._real_stream(repo, search)
 
1911
        client = repo._client
 
1912
        medium = client._medium
 
1913
        path = repo.bzrdir._path_for_remote_call(client)
 
1914
        search_bytes = repo._serialise_search_result(search)
 
1915
        args = (path, self.to_format.network_name())
 
1916
        candidate_verbs = [
 
1917
            ('Repository.get_stream_1.19', (1, 19)),
 
1918
            ('Repository.get_stream', (1, 13))]
 
1919
        found_verb = False
 
1920
        for verb, version in candidate_verbs:
 
1921
            if medium._is_remote_before(version):
 
1922
                continue
 
1923
            try:
 
1924
                response = repo._call_with_body_bytes_expecting_body(
 
1925
                    verb, args, search_bytes)
 
1926
            except errors.UnknownSmartMethod:
 
1927
                medium._remember_remote_is_before(version)
 
1928
            else:
 
1929
                response_tuple, response_handler = response
 
1930
                found_verb = True
 
1931
                break
 
1932
        if not found_verb:
 
1933
            return self._real_stream(repo, search)
 
1934
        if response_tuple[0] != 'ok':
 
1935
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1936
        byte_stream = response_handler.read_streamed_body()
 
1937
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
1938
        if src_format.network_name() != repo._format.network_name():
 
1939
            raise AssertionError(
 
1940
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
1941
                src_format.network_name(), repo._format.network_name()))
 
1942
        return stream
 
1943
 
 
1944
    def missing_parents_chain(self, search, sources):
 
1945
        """Chain multiple streams together to handle stacking.
 
1946
 
 
1947
        :param search: The overall search to satisfy with streams.
 
1948
        :param sources: A list of Repository objects to query.
 
1949
        """
 
1950
        self.from_serialiser = self.from_repository._format._serializer
 
1951
        self.seen_revs = set()
 
1952
        self.referenced_revs = set()
 
1953
        # If there are heads in the search, or the key count is > 0, we are not
 
1954
        # done.
 
1955
        while not search.is_empty() and len(sources) > 1:
 
1956
            source = sources.pop(0)
 
1957
            stream = self._get_stream(source, search)
 
1958
            for kind, substream in stream:
 
1959
                if kind != 'revisions':
 
1960
                    yield kind, substream
 
1961
                else:
 
1962
                    yield kind, self.missing_parents_rev_handler(substream)
 
1963
            search = search.refine(self.seen_revs, self.referenced_revs)
 
1964
            self.seen_revs = set()
 
1965
            self.referenced_revs = set()
 
1966
        if not search.is_empty():
 
1967
            for kind, stream in self._get_stream(sources[0], search):
 
1968
                yield kind, stream
 
1969
 
 
1970
    def missing_parents_rev_handler(self, substream):
 
1971
        for content in substream:
 
1972
            revision_bytes = content.get_bytes_as('fulltext')
 
1973
            revision = self.from_serialiser.read_revision_from_string(
 
1974
                revision_bytes)
 
1975
            self.seen_revs.add(content.key[-1])
 
1976
            self.referenced_revs.update(revision.parent_ids)
 
1977
            yield content
 
1978
 
 
1979
 
 
1980
class RemoteBranchLockableFiles(LockableFiles):
 
1981
    """A 'LockableFiles' implementation that talks to a smart server.
 
1982
 
 
1983
    This is not a public interface class.
 
1984
    """
 
1985
 
 
1986
    def __init__(self, bzrdir, _client):
 
1987
        self.bzrdir = bzrdir
 
1988
        self._client = _client
 
1989
        self._need_find_modes = True
 
1990
        LockableFiles.__init__(
 
1991
            self, bzrdir.get_branch_transport(None),
 
1992
            'lock', lockdir.LockDir)
 
1993
 
 
1994
    def _find_modes(self):
 
1995
        # RemoteBranches don't let the client set the mode of control files.
 
1996
        self._dir_mode = None
 
1997
        self._file_mode = None
 
1998
 
 
1999
 
 
2000
class RemoteBranchFormat(branch.BranchFormat):
 
2001
 
 
2002
    def __init__(self, network_name=None):
 
2003
        super(RemoteBranchFormat, self).__init__()
 
2004
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2005
        self._matchingbzrdir.set_branch_format(self)
 
2006
        self._custom_format = None
 
2007
        self._network_name = network_name
 
2008
 
 
2009
    def __eq__(self, other):
 
2010
        return (isinstance(other, RemoteBranchFormat) and
 
2011
            self.__dict__ == other.__dict__)
 
2012
 
 
2013
    def _ensure_real(self):
 
2014
        if self._custom_format is None:
 
2015
            self._custom_format = branch.network_format_registry.get(
 
2016
                self._network_name)
 
2017
 
 
2018
    def get_format_description(self):
 
2019
        self._ensure_real()
 
2020
        return 'Remote: ' + self._custom_format.get_format_description()
 
2021
 
 
2022
    def network_name(self):
 
2023
        return self._network_name
 
2024
 
 
2025
    def open(self, a_bzrdir, ignore_fallbacks=False):
 
2026
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
2027
 
 
2028
    def _vfs_initialize(self, a_bzrdir):
 
2029
        # Initialisation when using a local bzrdir object, or a non-vfs init
 
2030
        # method is not available on the server.
 
2031
        # self._custom_format is always set - the start of initialize ensures
 
2032
        # that.
 
2033
        if isinstance(a_bzrdir, RemoteBzrDir):
 
2034
            a_bzrdir._ensure_real()
 
2035
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
2036
        else:
 
2037
            # We assume the bzrdir is parameterised; it may not be.
 
2038
            result = self._custom_format.initialize(a_bzrdir)
 
2039
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
2040
            not isinstance(result, RemoteBranch)):
 
2041
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
2042
        return result
 
2043
 
 
2044
    def initialize(self, a_bzrdir):
 
2045
        # 1) get the network name to use.
 
2046
        if self._custom_format:
 
2047
            network_name = self._custom_format.network_name()
 
2048
        else:
 
2049
            # Select the current bzrlib default and ask for that.
 
2050
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
2051
            reference_format = reference_bzrdir_format.get_branch_format()
 
2052
            self._custom_format = reference_format
 
2053
            network_name = reference_format.network_name()
 
2054
        # Being asked to create on a non RemoteBzrDir:
 
2055
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
2056
            return self._vfs_initialize(a_bzrdir)
 
2057
        medium = a_bzrdir._client._medium
 
2058
        if medium._is_remote_before((1, 13)):
 
2059
            return self._vfs_initialize(a_bzrdir)
 
2060
        # Creating on a remote bzr dir.
 
2061
        # 2) try direct creation via RPC
 
2062
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2063
        verb = 'BzrDir.create_branch'
 
2064
        try:
 
2065
            response = a_bzrdir._call(verb, path, network_name)
 
2066
        except errors.UnknownSmartMethod:
 
2067
            # Fallback - use vfs methods
 
2068
            medium._remember_remote_is_before((1, 13))
 
2069
            return self._vfs_initialize(a_bzrdir)
 
2070
        if response[0] != 'ok':
 
2071
            raise errors.UnexpectedSmartServerResponse(response)
 
2072
        # Turn the response into a RemoteRepository object.
 
2073
        format = RemoteBranchFormat(network_name=response[1])
 
2074
        repo_format = response_tuple_to_repo_format(response[3:])
 
2075
        if response[2] == '':
 
2076
            repo_bzrdir = a_bzrdir
 
2077
        else:
 
2078
            repo_bzrdir = RemoteBzrDir(
 
2079
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2080
                a_bzrdir._client)
 
2081
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2082
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2083
            format=format, setup_stacking=False)
 
2084
        # XXX: We know this is a new branch, so it must have revno 0, revid
 
2085
        # NULL_REVISION. Creating the branch locked would make this be unable
 
2086
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
 
2087
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
 
2088
        return remote_branch
 
2089
 
 
2090
    def make_tags(self, branch):
 
2091
        self._ensure_real()
 
2092
        return self._custom_format.make_tags(branch)
 
2093
 
 
2094
    def supports_tags(self):
 
2095
        # Remote branches might support tags, but we won't know until we
 
2096
        # access the real remote branch.
 
2097
        self._ensure_real()
 
2098
        return self._custom_format.supports_tags()
 
2099
 
 
2100
    def supports_stacking(self):
 
2101
        self._ensure_real()
 
2102
        return self._custom_format.supports_stacking()
 
2103
 
 
2104
    def supports_set_append_revisions_only(self):
 
2105
        self._ensure_real()
 
2106
        return self._custom_format.supports_set_append_revisions_only()
 
2107
 
 
2108
 
 
2109
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
 
2110
    """Branch stored on a server accessed by HPSS RPC.
 
2111
 
 
2112
    At the moment most operations are mapped down to simple file operations.
 
2113
    """
 
2114
 
 
2115
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
2116
        _client=None, format=None, setup_stacking=True):
 
2117
        """Create a RemoteBranch instance.
 
2118
 
 
2119
        :param real_branch: An optional local implementation of the branch
 
2120
            format, usually accessing the data via the VFS.
 
2121
        :param _client: Private parameter for testing.
 
2122
        :param format: A RemoteBranchFormat object, None to create one
 
2123
            automatically. If supplied it should have a network_name already
 
2124
            supplied.
 
2125
        :param setup_stacking: If True make an RPC call to determine the
 
2126
            stacked (or not) status of the branch. If False assume the branch
 
2127
            is not stacked.
 
2128
        """
 
2129
        # We intentionally don't call the parent class's __init__, because it
 
2130
        # will try to assign to self.tags, which is a property in this subclass.
 
2131
        # And the parent's __init__ doesn't do much anyway.
 
2132
        self.bzrdir = remote_bzrdir
 
2133
        if _client is not None:
 
2134
            self._client = _client
 
2135
        else:
 
2136
            self._client = remote_bzrdir._client
 
2137
        self.repository = remote_repository
 
2138
        if real_branch is not None:
 
2139
            self._real_branch = real_branch
 
2140
            # Give the remote repository the matching real repo.
 
2141
            real_repo = self._real_branch.repository
 
2142
            if isinstance(real_repo, RemoteRepository):
 
2143
                real_repo._ensure_real()
 
2144
                real_repo = real_repo._real_repository
 
2145
            self.repository._set_real_repository(real_repo)
 
2146
            # Give the branch the remote repository to let fast-pathing happen.
 
2147
            self._real_branch.repository = self.repository
 
2148
        else:
 
2149
            self._real_branch = None
 
2150
        # Fill out expected attributes of branch for bzrlib API users.
 
2151
        self._clear_cached_state()
 
2152
        self.base = self.bzrdir.root_transport.base
 
2153
        self._control_files = None
 
2154
        self._lock_mode = None
 
2155
        self._lock_token = None
 
2156
        self._repo_lock_token = None
 
2157
        self._lock_count = 0
 
2158
        self._leave_lock = False
 
2159
        # Setup a format: note that we cannot call _ensure_real until all the
 
2160
        # attributes above are set: This code cannot be moved higher up in this
 
2161
        # function.
 
2162
        if format is None:
 
2163
            self._format = RemoteBranchFormat()
 
2164
            if real_branch is not None:
 
2165
                self._format._network_name = \
 
2166
                    self._real_branch._format.network_name()
 
2167
        else:
 
2168
            self._format = format
 
2169
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
 
2170
        # branch.open_branch method.
 
2171
        self._real_ignore_fallbacks = not setup_stacking
 
2172
        if not self._format._network_name:
 
2173
            # Did not get from open_branchV2 - old server.
 
2174
            self._ensure_real()
 
2175
            self._format._network_name = \
 
2176
                self._real_branch._format.network_name()
 
2177
        self.tags = self._format.make_tags(self)
 
2178
        # The base class init is not called, so we duplicate this:
 
2179
        hooks = branch.Branch.hooks['open']
 
2180
        for hook in hooks:
 
2181
            hook(self)
 
2182
        self._is_stacked = False
 
2183
        if setup_stacking:
 
2184
            self._setup_stacking()
 
2185
 
 
2186
    def _setup_stacking(self):
 
2187
        # configure stacking into the remote repository, by reading it from
 
2188
        # the vfs branch.
 
2189
        try:
 
2190
            fallback_url = self.get_stacked_on_url()
 
2191
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2192
            errors.UnstackableRepositoryFormat), e:
 
2193
            return
 
2194
        self._is_stacked = True
 
2195
        self._activate_fallback_location(fallback_url)
 
2196
 
 
2197
    def _get_config(self):
 
2198
        return RemoteBranchConfig(self)
 
2199
 
 
2200
    def _get_real_transport(self):
 
2201
        # if we try vfs access, return the real branch's vfs transport
 
2202
        self._ensure_real()
 
2203
        return self._real_branch._transport
 
2204
 
 
2205
    _transport = property(_get_real_transport)
 
2206
 
 
2207
    def __str__(self):
 
2208
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
2209
 
 
2210
    __repr__ = __str__
 
2211
 
 
2212
    def _ensure_real(self):
 
2213
        """Ensure that there is a _real_branch set.
 
2214
 
 
2215
        Used before calls to self._real_branch.
 
2216
        """
 
2217
        if self._real_branch is None:
 
2218
            if not vfs.vfs_enabled():
 
2219
                raise AssertionError('smart server vfs must be enabled '
 
2220
                    'to use vfs implementation')
 
2221
            self.bzrdir._ensure_real()
 
2222
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
2223
                ignore_fallbacks=self._real_ignore_fallbacks)
 
2224
            if self.repository._real_repository is None:
 
2225
                # Give the remote repository the matching real repo.
 
2226
                real_repo = self._real_branch.repository
 
2227
                if isinstance(real_repo, RemoteRepository):
 
2228
                    real_repo._ensure_real()
 
2229
                    real_repo = real_repo._real_repository
 
2230
                self.repository._set_real_repository(real_repo)
 
2231
            # Give the real branch the remote repository to let fast-pathing
 
2232
            # happen.
 
2233
            self._real_branch.repository = self.repository
 
2234
            if self._lock_mode == 'r':
 
2235
                self._real_branch.lock_read()
 
2236
            elif self._lock_mode == 'w':
 
2237
                self._real_branch.lock_write(token=self._lock_token)
 
2238
 
 
2239
    def _translate_error(self, err, **context):
 
2240
        self.repository._translate_error(err, branch=self, **context)
 
2241
 
 
2242
    def _clear_cached_state(self):
 
2243
        super(RemoteBranch, self)._clear_cached_state()
 
2244
        if self._real_branch is not None:
 
2245
            self._real_branch._clear_cached_state()
 
2246
 
 
2247
    def _clear_cached_state_of_remote_branch_only(self):
 
2248
        """Like _clear_cached_state, but doesn't clear the cache of
 
2249
        self._real_branch.
 
2250
 
 
2251
        This is useful when falling back to calling a method of
 
2252
        self._real_branch that changes state.  In that case the underlying
 
2253
        branch changes, so we need to invalidate this RemoteBranch's cache of
 
2254
        it.  However, there's no need to invalidate the _real_branch's cache
 
2255
        too, in fact doing so might harm performance.
 
2256
        """
 
2257
        super(RemoteBranch, self)._clear_cached_state()
 
2258
 
 
2259
    @property
 
2260
    def control_files(self):
 
2261
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
2262
        # because it triggers an _ensure_real that we otherwise might not need.
 
2263
        if self._control_files is None:
 
2264
            self._control_files = RemoteBranchLockableFiles(
 
2265
                self.bzrdir, self._client)
 
2266
        return self._control_files
 
2267
 
 
2268
    def _get_checkout_format(self):
 
2269
        self._ensure_real()
 
2270
        return self._real_branch._get_checkout_format()
 
2271
 
 
2272
    def get_physical_lock_status(self):
 
2273
        """See Branch.get_physical_lock_status()."""
 
2274
        # should be an API call to the server, as branches must be lockable.
 
2275
        self._ensure_real()
 
2276
        return self._real_branch.get_physical_lock_status()
 
2277
 
 
2278
    def get_stacked_on_url(self):
 
2279
        """Get the URL this branch is stacked against.
 
2280
 
 
2281
        :raises NotStacked: If the branch is not stacked.
 
2282
        :raises UnstackableBranchFormat: If the branch does not support
 
2283
            stacking.
 
2284
        :raises UnstackableRepositoryFormat: If the repository does not support
 
2285
            stacking.
 
2286
        """
 
2287
        try:
 
2288
            # there may not be a repository yet, so we can't use
 
2289
            # self._translate_error, so we can't use self._call either.
 
2290
            response = self._client.call('Branch.get_stacked_on_url',
 
2291
                self._remote_path())
 
2292
        except errors.ErrorFromSmartServer, err:
 
2293
            # there may not be a repository yet, so we can't call through
 
2294
            # its _translate_error
 
2295
            _translate_error(err, branch=self)
 
2296
        except errors.UnknownSmartMethod, err:
 
2297
            self._ensure_real()
 
2298
            return self._real_branch.get_stacked_on_url()
 
2299
        if response[0] != 'ok':
 
2300
            raise errors.UnexpectedSmartServerResponse(response)
 
2301
        return response[1]
 
2302
 
 
2303
    def set_stacked_on_url(self, url):
 
2304
        branch.Branch.set_stacked_on_url(self, url)
 
2305
        if not url:
 
2306
            self._is_stacked = False
 
2307
        else:
 
2308
            self._is_stacked = True
 
2309
        
 
2310
    def _vfs_get_tags_bytes(self):
 
2311
        self._ensure_real()
 
2312
        return self._real_branch._get_tags_bytes()
 
2313
 
 
2314
    def _get_tags_bytes(self):
 
2315
        medium = self._client._medium
 
2316
        if medium._is_remote_before((1, 13)):
 
2317
            return self._vfs_get_tags_bytes()
 
2318
        try:
 
2319
            response = self._call('Branch.get_tags_bytes', self._remote_path())
 
2320
        except errors.UnknownSmartMethod:
 
2321
            medium._remember_remote_is_before((1, 13))
 
2322
            return self._vfs_get_tags_bytes()
 
2323
        return response[0]
 
2324
 
 
2325
    def _vfs_set_tags_bytes(self, bytes):
 
2326
        self._ensure_real()
 
2327
        return self._real_branch._set_tags_bytes(bytes)
 
2328
 
 
2329
    def _set_tags_bytes(self, bytes):
 
2330
        medium = self._client._medium
 
2331
        if medium._is_remote_before((1, 18)):
 
2332
            self._vfs_set_tags_bytes(bytes)
 
2333
            return
 
2334
        try:
 
2335
            args = (
 
2336
                self._remote_path(), self._lock_token, self._repo_lock_token)
 
2337
            response = self._call_with_body_bytes(
 
2338
                'Branch.set_tags_bytes', args, bytes)
 
2339
        except errors.UnknownSmartMethod:
 
2340
            medium._remember_remote_is_before((1, 18))
 
2341
            self._vfs_set_tags_bytes(bytes)
 
2342
 
 
2343
    def lock_read(self):
 
2344
        self.repository.lock_read()
 
2345
        if not self._lock_mode:
 
2346
            self._note_lock('r')
 
2347
            self._lock_mode = 'r'
 
2348
            self._lock_count = 1
 
2349
            if self._real_branch is not None:
 
2350
                self._real_branch.lock_read()
 
2351
        else:
 
2352
            self._lock_count += 1
 
2353
 
 
2354
    def _remote_lock_write(self, token):
 
2355
        if token is None:
 
2356
            branch_token = repo_token = ''
 
2357
        else:
 
2358
            branch_token = token
 
2359
            repo_token = self.repository.lock_write()
 
2360
            self.repository.unlock()
 
2361
        err_context = {'token': token}
 
2362
        response = self._call(
 
2363
            'Branch.lock_write', self._remote_path(), branch_token,
 
2364
            repo_token or '', **err_context)
 
2365
        if response[0] != 'ok':
 
2366
            raise errors.UnexpectedSmartServerResponse(response)
 
2367
        ok, branch_token, repo_token = response
 
2368
        return branch_token, repo_token
 
2369
 
 
2370
    def lock_write(self, token=None):
 
2371
        if not self._lock_mode:
 
2372
            self._note_lock('w')
 
2373
            # Lock the branch and repo in one remote call.
 
2374
            remote_tokens = self._remote_lock_write(token)
 
2375
            self._lock_token, self._repo_lock_token = remote_tokens
 
2376
            if not self._lock_token:
 
2377
                raise SmartProtocolError('Remote server did not return a token!')
 
2378
            # Tell the self.repository object that it is locked.
 
2379
            self.repository.lock_write(
 
2380
                self._repo_lock_token, _skip_rpc=True)
 
2381
 
 
2382
            if self._real_branch is not None:
 
2383
                self._real_branch.lock_write(token=self._lock_token)
 
2384
            if token is not None:
 
2385
                self._leave_lock = True
 
2386
            else:
 
2387
                self._leave_lock = False
 
2388
            self._lock_mode = 'w'
 
2389
            self._lock_count = 1
 
2390
        elif self._lock_mode == 'r':
 
2391
            raise errors.ReadOnlyTransaction
 
2392
        else:
 
2393
            if token is not None:
 
2394
                # A token was given to lock_write, and we're relocking, so
 
2395
                # check that the given token actually matches the one we
 
2396
                # already have.
 
2397
                if token != self._lock_token:
 
2398
                    raise errors.TokenMismatch(token, self._lock_token)
 
2399
            self._lock_count += 1
 
2400
            # Re-lock the repository too.
 
2401
            self.repository.lock_write(self._repo_lock_token)
 
2402
        return self._lock_token or None
 
2403
 
 
2404
    def _unlock(self, branch_token, repo_token):
 
2405
        err_context = {'token': str((branch_token, repo_token))}
 
2406
        response = self._call(
 
2407
            'Branch.unlock', self._remote_path(), branch_token,
 
2408
            repo_token or '', **err_context)
 
2409
        if response == ('ok',):
 
2410
            return
 
2411
        raise errors.UnexpectedSmartServerResponse(response)
 
2412
 
 
2413
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
2414
    def unlock(self):
 
2415
        try:
 
2416
            self._lock_count -= 1
 
2417
            if not self._lock_count:
 
2418
                self._clear_cached_state()
 
2419
                mode = self._lock_mode
 
2420
                self._lock_mode = None
 
2421
                if self._real_branch is not None:
 
2422
                    if (not self._leave_lock and mode == 'w' and
 
2423
                        self._repo_lock_token):
 
2424
                        # If this RemoteBranch will remove the physical lock
 
2425
                        # for the repository, make sure the _real_branch
 
2426
                        # doesn't do it first.  (Because the _real_branch's
 
2427
                        # repository is set to be the RemoteRepository.)
 
2428
                        self._real_branch.repository.leave_lock_in_place()
 
2429
                    self._real_branch.unlock()
 
2430
                if mode != 'w':
 
2431
                    # Only write-locked branched need to make a remote method
 
2432
                    # call to perform the unlock.
 
2433
                    return
 
2434
                if not self._lock_token:
 
2435
                    raise AssertionError('Locked, but no token!')
 
2436
                branch_token = self._lock_token
 
2437
                repo_token = self._repo_lock_token
 
2438
                self._lock_token = None
 
2439
                self._repo_lock_token = None
 
2440
                if not self._leave_lock:
 
2441
                    self._unlock(branch_token, repo_token)
 
2442
        finally:
 
2443
            self.repository.unlock()
 
2444
 
 
2445
    def break_lock(self):
 
2446
        self._ensure_real()
 
2447
        return self._real_branch.break_lock()
 
2448
 
 
2449
    def leave_lock_in_place(self):
 
2450
        if not self._lock_token:
 
2451
            raise NotImplementedError(self.leave_lock_in_place)
 
2452
        self._leave_lock = True
 
2453
 
 
2454
    def dont_leave_lock_in_place(self):
 
2455
        if not self._lock_token:
 
2456
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
2457
        self._leave_lock = False
 
2458
 
 
2459
    @needs_read_lock
 
2460
    def get_rev_id(self, revno, history=None):
 
2461
        if revno == 0:
 
2462
            return _mod_revision.NULL_REVISION
 
2463
        last_revision_info = self.last_revision_info()
 
2464
        ok, result = self.repository.get_rev_id_for_revno(
 
2465
            revno, last_revision_info)
 
2466
        if ok:
 
2467
            return result
 
2468
        missing_parent = result[1]
 
2469
        # Either the revision named by the server is missing, or its parent
 
2470
        # is.  Call get_parent_map to determine which, so that we report a
 
2471
        # useful error.
 
2472
        parent_map = self.repository.get_parent_map([missing_parent])
 
2473
        if missing_parent in parent_map:
 
2474
            missing_parent = parent_map[missing_parent]
 
2475
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
2476
 
 
2477
    def _last_revision_info(self):
 
2478
        response = self._call('Branch.last_revision_info', self._remote_path())
 
2479
        if response[0] != 'ok':
 
2480
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2481
        revno = int(response[1])
 
2482
        last_revision = response[2]
 
2483
        return (revno, last_revision)
 
2484
 
 
2485
    def _gen_revision_history(self):
 
2486
        """See Branch._gen_revision_history()."""
 
2487
        if self._is_stacked:
 
2488
            self._ensure_real()
 
2489
            return self._real_branch._gen_revision_history()
 
2490
        response_tuple, response_handler = self._call_expecting_body(
 
2491
            'Branch.revision_history', self._remote_path())
 
2492
        if response_tuple[0] != 'ok':
 
2493
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2494
        result = response_handler.read_body_bytes().split('\x00')
 
2495
        if result == ['']:
 
2496
            return []
 
2497
        return result
 
2498
 
 
2499
    def _remote_path(self):
 
2500
        return self.bzrdir._path_for_remote_call(self._client)
 
2501
 
 
2502
    def _set_last_revision_descendant(self, revision_id, other_branch,
 
2503
            allow_diverged=False, allow_overwrite_descendant=False):
 
2504
        # This performs additional work to meet the hook contract; while its
 
2505
        # undesirable, we have to synthesise the revno to call the hook, and
 
2506
        # not calling the hook is worse as it means changes can't be prevented.
 
2507
        # Having calculated this though, we can't just call into
 
2508
        # set_last_revision_info as a simple call, because there is a set_rh
 
2509
        # hook that some folk may still be using.
 
2510
        old_revno, old_revid = self.last_revision_info()
 
2511
        history = self._lefthand_history(revision_id)
 
2512
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
2513
        err_context = {'other_branch': other_branch}
 
2514
        response = self._call('Branch.set_last_revision_ex',
 
2515
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2516
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
2517
            **err_context)
 
2518
        self._clear_cached_state()
 
2519
        if len(response) != 3 and response[0] != 'ok':
 
2520
            raise errors.UnexpectedSmartServerResponse(response)
 
2521
        new_revno, new_revision_id = response[1:]
 
2522
        self._last_revision_info_cache = new_revno, new_revision_id
 
2523
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2524
        if self._real_branch is not None:
 
2525
            cache = new_revno, new_revision_id
 
2526
            self._real_branch._last_revision_info_cache = cache
 
2527
 
 
2528
    def _set_last_revision(self, revision_id):
 
2529
        old_revno, old_revid = self.last_revision_info()
 
2530
        # This performs additional work to meet the hook contract; while its
 
2531
        # undesirable, we have to synthesise the revno to call the hook, and
 
2532
        # not calling the hook is worse as it means changes can't be prevented.
 
2533
        # Having calculated this though, we can't just call into
 
2534
        # set_last_revision_info as a simple call, because there is a set_rh
 
2535
        # hook that some folk may still be using.
 
2536
        history = self._lefthand_history(revision_id)
 
2537
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
2538
        self._clear_cached_state()
 
2539
        response = self._call('Branch.set_last_revision',
 
2540
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2541
            revision_id)
 
2542
        if response != ('ok',):
 
2543
            raise errors.UnexpectedSmartServerResponse(response)
 
2544
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2545
 
 
2546
    @needs_write_lock
 
2547
    def set_revision_history(self, rev_history):
 
2548
        # Send just the tip revision of the history; the server will generate
 
2549
        # the full history from that.  If the revision doesn't exist in this
 
2550
        # branch, NoSuchRevision will be raised.
 
2551
        if rev_history == []:
 
2552
            rev_id = 'null:'
 
2553
        else:
 
2554
            rev_id = rev_history[-1]
 
2555
        self._set_last_revision(rev_id)
 
2556
        for hook in branch.Branch.hooks['set_rh']:
 
2557
            hook(self, rev_history)
 
2558
        self._cache_revision_history(rev_history)
 
2559
 
 
2560
    def _get_parent_location(self):
 
2561
        medium = self._client._medium
 
2562
        if medium._is_remote_before((1, 13)):
 
2563
            return self._vfs_get_parent_location()
 
2564
        try:
 
2565
            response = self._call('Branch.get_parent', self._remote_path())
 
2566
        except errors.UnknownSmartMethod:
 
2567
            medium._remember_remote_is_before((1, 13))
 
2568
            return self._vfs_get_parent_location()
 
2569
        if len(response) != 1:
 
2570
            raise errors.UnexpectedSmartServerResponse(response)
 
2571
        parent_location = response[0]
 
2572
        if parent_location == '':
 
2573
            return None
 
2574
        return parent_location
 
2575
 
 
2576
    def _vfs_get_parent_location(self):
 
2577
        self._ensure_real()
 
2578
        return self._real_branch._get_parent_location()
 
2579
 
 
2580
    def _set_parent_location(self, url):
 
2581
        medium = self._client._medium
 
2582
        if medium._is_remote_before((1, 15)):
 
2583
            return self._vfs_set_parent_location(url)
 
2584
        try:
 
2585
            call_url = url or ''
 
2586
            if type(call_url) is not str:
 
2587
                raise AssertionError('url must be a str or None (%s)' % url)
 
2588
            response = self._call('Branch.set_parent_location',
 
2589
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2590
                call_url)
 
2591
        except errors.UnknownSmartMethod:
 
2592
            medium._remember_remote_is_before((1, 15))
 
2593
            return self._vfs_set_parent_location(url)
 
2594
        if response != ():
 
2595
            raise errors.UnexpectedSmartServerResponse(response)
 
2596
 
 
2597
    def _vfs_set_parent_location(self, url):
 
2598
        self._ensure_real()
 
2599
        return self._real_branch._set_parent_location(url)
 
2600
 
 
2601
    @needs_write_lock
 
2602
    def pull(self, source, overwrite=False, stop_revision=None,
 
2603
             **kwargs):
 
2604
        self._clear_cached_state_of_remote_branch_only()
 
2605
        self._ensure_real()
 
2606
        return self._real_branch.pull(
 
2607
            source, overwrite=overwrite, stop_revision=stop_revision,
 
2608
            _override_hook_target=self, **kwargs)
 
2609
 
 
2610
    @needs_read_lock
 
2611
    def push(self, target, overwrite=False, stop_revision=None):
 
2612
        self._ensure_real()
 
2613
        return self._real_branch.push(
 
2614
            target, overwrite=overwrite, stop_revision=stop_revision,
 
2615
            _override_hook_source_branch=self)
 
2616
 
 
2617
    def is_locked(self):
 
2618
        return self._lock_count >= 1
 
2619
 
 
2620
    @needs_read_lock
 
2621
    def revision_id_to_revno(self, revision_id):
 
2622
        self._ensure_real()
 
2623
        return self._real_branch.revision_id_to_revno(revision_id)
 
2624
 
 
2625
    @needs_write_lock
 
2626
    def set_last_revision_info(self, revno, revision_id):
 
2627
        # XXX: These should be returned by the set_last_revision_info verb
 
2628
        old_revno, old_revid = self.last_revision_info()
 
2629
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2630
        revision_id = ensure_null(revision_id)
 
2631
        try:
 
2632
            response = self._call('Branch.set_last_revision_info',
 
2633
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2634
                str(revno), revision_id)
 
2635
        except errors.UnknownSmartMethod:
 
2636
            self._ensure_real()
 
2637
            self._clear_cached_state_of_remote_branch_only()
 
2638
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2639
            self._last_revision_info_cache = revno, revision_id
 
2640
            return
 
2641
        if response == ('ok',):
 
2642
            self._clear_cached_state()
 
2643
            self._last_revision_info_cache = revno, revision_id
 
2644
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2645
            # Update the _real_branch's cache too.
 
2646
            if self._real_branch is not None:
 
2647
                cache = self._last_revision_info_cache
 
2648
                self._real_branch._last_revision_info_cache = cache
 
2649
        else:
 
2650
            raise errors.UnexpectedSmartServerResponse(response)
 
2651
 
 
2652
    @needs_write_lock
 
2653
    def generate_revision_history(self, revision_id, last_rev=None,
 
2654
                                  other_branch=None):
 
2655
        medium = self._client._medium
 
2656
        if not medium._is_remote_before((1, 6)):
 
2657
            # Use a smart method for 1.6 and above servers
 
2658
            try:
 
2659
                self._set_last_revision_descendant(revision_id, other_branch,
 
2660
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2661
                return
 
2662
            except errors.UnknownSmartMethod:
 
2663
                medium._remember_remote_is_before((1, 6))
 
2664
        self._clear_cached_state_of_remote_branch_only()
 
2665
        self.set_revision_history(self._lefthand_history(revision_id,
 
2666
            last_rev=last_rev,other_branch=other_branch))
 
2667
 
 
2668
    def set_push_location(self, location):
 
2669
        self._ensure_real()
 
2670
        return self._real_branch.set_push_location(location)
 
2671
 
 
2672
 
 
2673
class RemoteConfig(object):
 
2674
    """A Config that reads and writes from smart verbs.
 
2675
 
 
2676
    It is a low-level object that considers config data to be name/value pairs
 
2677
    that may be associated with a section. Assigning meaning to the these
 
2678
    values is done at higher levels like bzrlib.config.TreeConfig.
 
2679
    """
 
2680
 
 
2681
    def get_option(self, name, section=None, default=None):
 
2682
        """Return the value associated with a named option.
 
2683
 
 
2684
        :param name: The name of the value
 
2685
        :param section: The section the option is in (if any)
 
2686
        :param default: The value to return if the value is not set
 
2687
        :return: The value or default value
 
2688
        """
 
2689
        try:
 
2690
            configobj = self._get_configobj()
 
2691
            if section is None:
 
2692
                section_obj = configobj
 
2693
            else:
 
2694
                try:
 
2695
                    section_obj = configobj[section]
 
2696
                except KeyError:
 
2697
                    return default
 
2698
            return section_obj.get(name, default)
 
2699
        except errors.UnknownSmartMethod:
 
2700
            return self._vfs_get_option(name, section, default)
 
2701
 
 
2702
    def _response_to_configobj(self, response):
 
2703
        if len(response[0]) and response[0][0] != 'ok':
 
2704
            raise errors.UnexpectedSmartServerResponse(response)
 
2705
        lines = response[1].read_body_bytes().splitlines()
 
2706
        return config.ConfigObj(lines, encoding='utf-8')
 
2707
 
 
2708
 
 
2709
class RemoteBranchConfig(RemoteConfig):
 
2710
    """A RemoteConfig for Branches."""
 
2711
 
 
2712
    def __init__(self, branch):
 
2713
        self._branch = branch
 
2714
 
 
2715
    def _get_configobj(self):
 
2716
        path = self._branch._remote_path()
 
2717
        response = self._branch._client.call_expecting_body(
 
2718
            'Branch.get_config_file', path)
 
2719
        return self._response_to_configobj(response)
 
2720
 
 
2721
    def set_option(self, value, name, section=None):
 
2722
        """Set the value associated with a named option.
 
2723
 
 
2724
        :param value: The value to set
 
2725
        :param name: The name of the value to set
 
2726
        :param section: The section the option is in (if any)
 
2727
        """
 
2728
        medium = self._branch._client._medium
 
2729
        if medium._is_remote_before((1, 14)):
 
2730
            return self._vfs_set_option(value, name, section)
 
2731
        try:
 
2732
            path = self._branch._remote_path()
 
2733
            response = self._branch._client.call('Branch.set_config_option',
 
2734
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2735
                value.encode('utf8'), name, section or '')
 
2736
        except errors.UnknownSmartMethod:
 
2737
            medium._remember_remote_is_before((1, 14))
 
2738
            return self._vfs_set_option(value, name, section)
 
2739
        if response != ():
 
2740
            raise errors.UnexpectedSmartServerResponse(response)
 
2741
 
 
2742
    def _real_object(self):
 
2743
        self._branch._ensure_real()
 
2744
        return self._branch._real_branch
 
2745
 
 
2746
    def _vfs_set_option(self, value, name, section=None):
 
2747
        return self._real_object()._get_config().set_option(
 
2748
            value, name, section)
 
2749
 
 
2750
 
 
2751
class RemoteBzrDirConfig(RemoteConfig):
 
2752
    """A RemoteConfig for BzrDirs."""
 
2753
 
 
2754
    def __init__(self, bzrdir):
 
2755
        self._bzrdir = bzrdir
 
2756
 
 
2757
    def _get_configobj(self):
 
2758
        medium = self._bzrdir._client._medium
 
2759
        verb = 'BzrDir.get_config_file'
 
2760
        if medium._is_remote_before((1, 15)):
 
2761
            raise errors.UnknownSmartMethod(verb)
 
2762
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
 
2763
        response = self._bzrdir._call_expecting_body(
 
2764
            verb, path)
 
2765
        return self._response_to_configobj(response)
 
2766
 
 
2767
    def _vfs_get_option(self, name, section, default):
 
2768
        return self._real_object()._get_config().get_option(
 
2769
            name, section, default)
 
2770
 
 
2771
    def set_option(self, value, name, section=None):
 
2772
        """Set the value associated with a named option.
 
2773
 
 
2774
        :param value: The value to set
 
2775
        :param name: The name of the value to set
 
2776
        :param section: The section the option is in (if any)
 
2777
        """
 
2778
        return self._real_object()._get_config().set_option(
 
2779
            value, name, section)
 
2780
 
 
2781
    def _real_object(self):
 
2782
        self._bzrdir._ensure_real()
 
2783
        return self._bzrdir._real_bzrdir
 
2784
 
 
2785
 
 
2786
 
 
2787
def _extract_tar(tar, to_dir):
 
2788
    """Extract all the contents of a tarfile object.
 
2789
 
 
2790
    A replacement for extractall, which is not present in python2.4
 
2791
    """
 
2792
    for tarinfo in tar:
 
2793
        tar.extract(tarinfo, to_dir)
 
2794
 
 
2795
 
 
2796
def _translate_error(err, **context):
 
2797
    """Translate an ErrorFromSmartServer into a more useful error.
 
2798
 
 
2799
    Possible context keys:
 
2800
      - branch
 
2801
      - repository
 
2802
      - bzrdir
 
2803
      - token
 
2804
      - other_branch
 
2805
      - path
 
2806
 
 
2807
    If the error from the server doesn't match a known pattern, then
 
2808
    UnknownErrorFromSmartServer is raised.
 
2809
    """
 
2810
    def find(name):
 
2811
        try:
 
2812
            return context[name]
 
2813
        except KeyError, key_err:
 
2814
            mutter('Missing key %r in context %r', key_err.args[0], context)
 
2815
            raise err
 
2816
    def get_path():
 
2817
        """Get the path from the context if present, otherwise use first error
 
2818
        arg.
 
2819
        """
 
2820
        try:
 
2821
            return context['path']
 
2822
        except KeyError, key_err:
 
2823
            try:
 
2824
                return err.error_args[0]
 
2825
            except IndexError, idx_err:
 
2826
                mutter(
 
2827
                    'Missing key %r in context %r', key_err.args[0], context)
 
2828
                raise err
 
2829
 
 
2830
    if err.error_verb == 'IncompatibleRepositories':
 
2831
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2832
            err.error_args[1], err.error_args[2])
 
2833
    elif err.error_verb == 'NoSuchRevision':
 
2834
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2835
    elif err.error_verb == 'nosuchrevision':
 
2836
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2837
    elif err.error_verb == 'nobranch':
 
2838
        if len(err.error_args) >= 1:
 
2839
            extra = err.error_args[0]
 
2840
        else:
 
2841
            extra = None
 
2842
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2843
            detail=extra)
 
2844
    elif err.error_verb == 'norepository':
 
2845
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2846
    elif err.error_verb == 'LockContention':
 
2847
        raise errors.LockContention('(remote lock)')
 
2848
    elif err.error_verb == 'UnlockableTransport':
 
2849
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2850
    elif err.error_verb == 'LockFailed':
 
2851
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2852
    elif err.error_verb == 'TokenMismatch':
 
2853
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2854
    elif err.error_verb == 'Diverged':
 
2855
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2856
    elif err.error_verb == 'TipChangeRejected':
 
2857
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2858
    elif err.error_verb == 'UnstackableBranchFormat':
 
2859
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2860
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2861
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2862
    elif err.error_verb == 'NotStacked':
 
2863
        raise errors.NotStacked(branch=find('branch'))
 
2864
    elif err.error_verb == 'PermissionDenied':
 
2865
        path = get_path()
 
2866
        if len(err.error_args) >= 2:
 
2867
            extra = err.error_args[1]
 
2868
        else:
 
2869
            extra = None
 
2870
        raise errors.PermissionDenied(path, extra=extra)
 
2871
    elif err.error_verb == 'ReadError':
 
2872
        path = get_path()
 
2873
        raise errors.ReadError(path)
 
2874
    elif err.error_verb == 'NoSuchFile':
 
2875
        path = get_path()
 
2876
        raise errors.NoSuchFile(path)
 
2877
    elif err.error_verb == 'FileExists':
 
2878
        raise errors.FileExists(err.error_args[0])
 
2879
    elif err.error_verb == 'DirectoryNotEmpty':
 
2880
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2881
    elif err.error_verb == 'ShortReadvError':
 
2882
        args = err.error_args
 
2883
        raise errors.ShortReadvError(
 
2884
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2885
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2886
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2887
        val = err.error_args[1]
 
2888
        start = int(err.error_args[2])
 
2889
        end = int(err.error_args[3])
 
2890
        reason = str(err.error_args[4]) # reason must always be a string
 
2891
        if val.startswith('u:'):
 
2892
            val = val[2:].decode('utf-8')
 
2893
        elif val.startswith('s:'):
 
2894
            val = val[2:].decode('base64')
 
2895
        if err.error_verb == 'UnicodeDecodeError':
 
2896
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2897
        elif err.error_verb == 'UnicodeEncodeError':
 
2898
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
2899
    elif err.error_verb == 'ReadOnlyError':
 
2900
        raise errors.TransportNotPossible('readonly transport')
 
2901
    raise errors.UnknownErrorFromSmartServer(err)