/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Andrew Bennetts
  • Date: 2009-02-20 06:16:22 UTC
  • mfrom: (4023 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4027.
  • Revision ID: andrew.bennetts@canonical.com-20090220061622-te9miq29rlfqiwwv
Merge bzr.dev.

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