~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-06-30 17:12:32 UTC
  • mfrom: (4398.3.2 register-serializer9)
  • Revision ID: pqm@pqm.ubuntu.com-20090630171232-h997nd3w5w89v60t
(Jelmer) Pass create_prefix paremeter to BzrDir.push_branch.

Show diffs side-by-side

added added

removed removed

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