/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: Martin Pool
  • Date: 2009-03-09 12:58:02 UTC
  • mto: This revision was merged to the branch mainline in revision 4099.
  • Revision ID: mbp@sourcefrog.net-20090309125802-guvsapvb980yt85n
Add test for failures inside pyrex readdir

Show diffs side-by-side

added added

removed removed

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