/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: Canonical.com Patch Queue Manager
  • Date: 2008-09-06 10:25:39 UTC
  • mfrom: (3690.1.2 nicer-error)
  • Revision ID: pqm@pqm.ubuntu.com-20080906102539-ss1fkx2csdcalqlc
Do not traceback on unexpected error responses from a smart server.
        (Andrew Bennetts)

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