/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: John Arbash Meinel
  • Date: 2008-12-11 03:08:03 UTC
  • mto: This revision was merged to the branch mainline in revision 3895.
  • Revision ID: john@arbash-meinel.com-20081211030803-gctunob7zsten3qg
Move everything into properly parameterized tests.

Also add tests that we preserve the object when it is already lines.

The compiled form takes 450us on a 7.6k line file (NEWS).
So for common cases, we should have virtually no overhead.

Show diffs side-by-side

added added

removed removed

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