~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2005-08-23 06:52:09 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050823065209-81cd5962c401751b
move io redirection into each test case from the global runner

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