/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2009-02-25 00:31:09 UTC
  • mfrom: (4043 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4044.
  • Revision ID: robertc@robertcollins.net-20090225003109-9ngqolksoevjw5ay
Merge bzr.dev, adjust effort tests reducing the effort in line with the write_group avoidance.

Show diffs side-by-side

added added

removed removed

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