/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-23 07:25:27 UTC
  • mfrom: (4183 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4189.
  • Revision ID: mbp@sourcefrog.net-20090323072527-317my4n8zej1g6v9
merge trunk

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