/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Andrew Bennetts
  • Date: 2008-10-29 10:42:48 UTC
  • mto: This revision was merged to the branch mainline in revision 3814.
  • Revision ID: andrew.bennetts@canonical.com-20081029104248-cm1i6byb3ht8x2hh
Add tests for autopack RPC.

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