~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Pool
  • Date: 2007-11-29 07:12:42 UTC
  • mto: This revision was merged to the branch mainline in revision 3048.
  • Revision ID: mbp@sourcefrog.net-20071129071242-1tcn2a18547udlvw
Fix up calls to TestCase.build_tree passing a string rather than a list

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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
from cStringIO import StringIO
 
21
 
 
22
from bzrlib import (
 
23
    branch,
 
24
    errors,
 
25
    lockdir,
 
26
    repository,
 
27
    revision,
 
28
)
 
29
from bzrlib.branch import Branch, BranchReferenceFormat
 
30
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
31
from bzrlib.config import BranchConfig, TreeConfig
 
32
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
33
from bzrlib.errors import NoSuchRevision
 
34
from bzrlib.lockable_files import LockableFiles
 
35
from bzrlib.pack import ContainerReader
 
36
from bzrlib.smart import client, vfs
 
37
from bzrlib.symbol_versioning import (
 
38
    deprecated_method,
 
39
    zero_ninetyone,
 
40
    )
 
41
from bzrlib.trace import note
 
42
 
 
43
# Note: RemoteBzrDirFormat is in bzrdir.py
 
44
 
 
45
class RemoteBzrDir(BzrDir):
 
46
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
47
 
 
48
    def __init__(self, transport, _client=None):
 
49
        """Construct a RemoteBzrDir.
 
50
 
 
51
        :param _client: Private parameter for testing. Disables probing and the
 
52
            use of a real bzrdir.
 
53
        """
 
54
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
55
        # this object holds a delegated bzrdir that uses file-level operations
 
56
        # to talk to the other side
 
57
        self._real_bzrdir = None
 
58
 
 
59
        if _client is None:
 
60
            self._shared_medium = transport.get_shared_medium()
 
61
            self._client = client._SmartClient(self._shared_medium)
 
62
        else:
 
63
            self._client = _client
 
64
            self._shared_medium = None
 
65
            return
 
66
 
 
67
        path = self._path_for_remote_call(self._client)
 
68
        response = self._client.call('BzrDir.open', path)
 
69
        if response not in [('yes',), ('no',)]:
 
70
            raise errors.UnexpectedSmartServerResponse(response)
 
71
        if response == ('no',):
 
72
            raise errors.NotBranchError(path=transport.base)
 
73
 
 
74
    def _ensure_real(self):
 
75
        """Ensure that there is a _real_bzrdir set.
 
76
 
 
77
        Used before calls to self._real_bzrdir.
 
78
        """
 
79
        if not self._real_bzrdir:
 
80
            self._real_bzrdir = BzrDir.open_from_transport(
 
81
                self.root_transport, _server_formats=False)
 
82
 
 
83
    def create_repository(self, shared=False):
 
84
        self._ensure_real()
 
85
        self._real_bzrdir.create_repository(shared=shared)
 
86
        return self.open_repository()
 
87
 
 
88
    def create_branch(self):
 
89
        self._ensure_real()
 
90
        real_branch = self._real_bzrdir.create_branch()
 
91
        return RemoteBranch(self, self.find_repository(), real_branch)
 
92
 
 
93
    def destroy_branch(self):
 
94
        """See BzrDir.destroy_branch"""
 
95
        self._ensure_real()
 
96
        self._real_bzrdir.destroy_branch()
 
97
 
 
98
    def create_workingtree(self, revision_id=None, from_branch=None):
 
99
        raise errors.NotLocalUrl(self.transport.base)
 
100
 
 
101
    def find_branch_format(self):
 
102
        """Find the branch 'format' for this bzrdir.
 
103
 
 
104
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
105
        """
 
106
        b = self.open_branch()
 
107
        return b._format
 
108
 
 
109
    def get_branch_reference(self):
 
110
        """See BzrDir.get_branch_reference()."""
 
111
        path = self._path_for_remote_call(self._client)
 
112
        response = self._client.call('BzrDir.open_branch', path)
 
113
        if response[0] == 'ok':
 
114
            if response[1] == '':
 
115
                # branch at this location.
 
116
                return None
 
117
            else:
 
118
                # a branch reference, use the existing BranchReference logic.
 
119
                return response[1]
 
120
        elif response == ('nobranch',):
 
121
            raise errors.NotBranchError(path=self.root_transport.base)
 
122
        else:
 
123
            raise errors.UnexpectedSmartServerResponse(response)
 
124
 
 
125
    def open_branch(self, _unsupported=False):
 
126
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
127
        reference_url = self.get_branch_reference()
 
128
        if reference_url is None:
 
129
            # branch at this location.
 
130
            return RemoteBranch(self, self.find_repository())
 
131
        else:
 
132
            # a branch reference, use the existing BranchReference logic.
 
133
            format = BranchReferenceFormat()
 
134
            return format.open(self, _found=True, location=reference_url)
 
135
                
 
136
    def open_repository(self):
 
137
        path = self._path_for_remote_call(self._client)
 
138
        response = self._client.call('BzrDir.find_repository', path)
 
139
        assert response[0] in ('ok', 'norepository'), \
 
140
            'unexpected response code %s' % (response,)
 
141
        if response[0] == 'norepository':
 
142
            raise errors.NoRepositoryPresent(self)
 
143
        assert len(response) == 4, 'incorrect response length %s' % (response,)
 
144
        if response[1] == '':
 
145
            format = RemoteRepositoryFormat()
 
146
            format.rich_root_data = (response[2] == 'yes')
 
147
            format.supports_tree_reference = (response[3] == 'yes')
 
148
            return RemoteRepository(self, format)
 
149
        else:
 
150
            raise errors.NoRepositoryPresent(self)
 
151
 
 
152
    def open_workingtree(self, recommend_upgrade=True):
 
153
        self._ensure_real()
 
154
        if self._real_bzrdir.has_workingtree():
 
155
            raise errors.NotLocalUrl(self.root_transport)
 
156
        else:
 
157
            raise errors.NoWorkingTree(self.root_transport.base)
 
158
 
 
159
    def _path_for_remote_call(self, client):
 
160
        """Return the path to be used for this bzrdir in a remote call."""
 
161
        return client.remote_path_from_transport(self.root_transport)
 
162
 
 
163
    def get_branch_transport(self, branch_format):
 
164
        self._ensure_real()
 
165
        return self._real_bzrdir.get_branch_transport(branch_format)
 
166
 
 
167
    def get_repository_transport(self, repository_format):
 
168
        self._ensure_real()
 
169
        return self._real_bzrdir.get_repository_transport(repository_format)
 
170
 
 
171
    def get_workingtree_transport(self, workingtree_format):
 
172
        self._ensure_real()
 
173
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
174
 
 
175
    def can_convert_format(self):
 
176
        """Upgrading of remote bzrdirs is not supported yet."""
 
177
        return False
 
178
 
 
179
    def needs_format_conversion(self, format=None):
 
180
        """Upgrading of remote bzrdirs is not supported yet."""
 
181
        return False
 
182
 
 
183
    def clone(self, url, revision_id=None, force_new_repo=False):
 
184
        self._ensure_real()
 
185
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
186
            force_new_repo=force_new_repo)
 
187
 
 
188
 
 
189
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
190
    """Format for repositories accessed over a _SmartClient.
 
191
 
 
192
    Instances of this repository are represented by RemoteRepository
 
193
    instances.
 
194
 
 
195
    The RemoteRepositoryFormat is parameterised during construction
 
196
    to reflect the capabilities of the real, remote format. Specifically
 
197
    the attributes rich_root_data and supports_tree_reference are set
 
198
    on a per instance basis, and are not set (and should not be) at
 
199
    the class level.
 
200
    """
 
201
 
 
202
    _matchingbzrdir = RemoteBzrDirFormat
 
203
 
 
204
    def initialize(self, a_bzrdir, shared=False):
 
205
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
206
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
 
207
        return a_bzrdir.create_repository(shared=shared)
 
208
    
 
209
    def open(self, a_bzrdir):
 
210
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
211
        return a_bzrdir.open_repository()
 
212
 
 
213
    def get_format_description(self):
 
214
        return 'bzr remote repository'
 
215
 
 
216
    def __eq__(self, other):
 
217
        return self.__class__ == other.__class__
 
218
 
 
219
    def check_conversion_target(self, target_format):
 
220
        if self.rich_root_data and not target_format.rich_root_data:
 
221
            raise errors.BadConversionTarget(
 
222
                'Does not support rich root data.', target_format)
 
223
        if (self.supports_tree_reference and
 
224
            not getattr(target_format, 'supports_tree_reference', False)):
 
225
            raise errors.BadConversionTarget(
 
226
                'Does not support nested trees', target_format)
 
227
 
 
228
 
 
229
class RemoteRepository(object):
 
230
    """Repository accessed over rpc.
 
231
 
 
232
    For the moment most operations are performed using local transport-backed
 
233
    Repository objects.
 
234
    """
 
235
 
 
236
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
237
        """Create a RemoteRepository instance.
 
238
        
 
239
        :param remote_bzrdir: The bzrdir hosting this repository.
 
240
        :param format: The RemoteFormat object to use.
 
241
        :param real_repository: If not None, a local implementation of the
 
242
            repository logic for the repository, usually accessing the data
 
243
            via the VFS.
 
244
        :param _client: Private testing parameter - override the smart client
 
245
            to be used by the repository.
 
246
        """
 
247
        if real_repository:
 
248
            self._real_repository = real_repository
 
249
        else:
 
250
            self._real_repository = None
 
251
        self.bzrdir = remote_bzrdir
 
252
        if _client is None:
 
253
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
254
        else:
 
255
            self._client = _client
 
256
        self._format = format
 
257
        self._lock_mode = None
 
258
        self._lock_token = None
 
259
        self._lock_count = 0
 
260
        self._leave_lock = False
 
261
        # For tests:
 
262
        # These depend on the actual remote format, so force them off for
 
263
        # maximum compatibility. XXX: In future these should depend on the
 
264
        # remote repository instance, but this is irrelevant until we perform
 
265
        # reconcile via an RPC call.
 
266
        self._reconcile_does_inventory_gc = False
 
267
        self._reconcile_fixes_text_parents = False
 
268
        self._reconcile_backsup_inventory = False
 
269
        self.base = self.bzrdir.transport.base
 
270
 
 
271
    def __str__(self):
 
272
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
273
 
 
274
    __repr__ = __str__
 
275
 
 
276
    def abort_write_group(self):
 
277
        """Complete a write group on the decorated repository.
 
278
        
 
279
        Smart methods peform operations in a single step so this api
 
280
        is not really applicable except as a compatibility thunk
 
281
        for older plugins that don't use e.g. the CommitBuilder
 
282
        facility.
 
283
        """
 
284
        self._ensure_real()
 
285
        return self._real_repository.abort_write_group()
 
286
 
 
287
    def commit_write_group(self):
 
288
        """Complete a write group on the decorated repository.
 
289
        
 
290
        Smart methods peform operations in a single step so this api
 
291
        is not really applicable except as a compatibility thunk
 
292
        for older plugins that don't use e.g. the CommitBuilder
 
293
        facility.
 
294
        """
 
295
        self._ensure_real()
 
296
        return self._real_repository.commit_write_group()
 
297
 
 
298
    def _ensure_real(self):
 
299
        """Ensure that there is a _real_repository set.
 
300
 
 
301
        Used before calls to self._real_repository.
 
302
        """
 
303
        if not self._real_repository:
 
304
            self.bzrdir._ensure_real()
 
305
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
306
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
307
 
 
308
    def find_text_key_references(self):
 
309
        """Find the text key references within the repository.
 
310
 
 
311
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
312
        revision_ids. Each altered file-ids has the exact revision_ids that
 
313
        altered it listed explicitly.
 
314
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
315
            to whether they were referred to by the inventory of the
 
316
            revision_id that they contain. The inventory texts from all present
 
317
            revision ids are assessed to generate this report.
 
318
        """
 
319
        self._ensure_real()
 
320
        return self._real_repository.find_text_key_references()
 
321
 
 
322
    def _generate_text_key_index(self):
 
323
        """Generate a new text key index for the repository.
 
324
 
 
325
        This is an expensive function that will take considerable time to run.
 
326
 
 
327
        :return: A dict mapping (file_id, revision_id) tuples to a list of
 
328
            parents, also (file_id, revision_id) tuples.
 
329
        """
 
330
        self._ensure_real()
 
331
        return self._real_repository._generate_text_key_index()
 
332
 
 
333
    def get_revision_graph(self, revision_id=None):
 
334
        """See Repository.get_revision_graph()."""
 
335
        if revision_id is None:
 
336
            revision_id = ''
 
337
        elif revision.is_null(revision_id):
 
338
            return {}
 
339
 
 
340
        path = self.bzrdir._path_for_remote_call(self._client)
 
341
        assert type(revision_id) is str
 
342
        response = self._client.call_expecting_body(
 
343
            'Repository.get_revision_graph', path, revision_id)
 
344
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
345
            raise errors.UnexpectedSmartServerResponse(response[0])
 
346
        if response[0][0] == 'ok':
 
347
            coded = response[1].read_body_bytes()
 
348
            if coded == '':
 
349
                # no revisions in this repository!
 
350
                return {}
 
351
            lines = coded.split('\n')
 
352
            revision_graph = {}
 
353
            for line in lines:
 
354
                d = tuple(line.split())
 
355
                revision_graph[d[0]] = d[1:]
 
356
                
 
357
            return revision_graph
 
358
        else:
 
359
            response_body = response[1].read_body_bytes()
 
360
            assert response_body == ''
 
361
            raise NoSuchRevision(self, revision_id)
 
362
 
 
363
    def has_revision(self, revision_id):
 
364
        """See Repository.has_revision()."""
 
365
        if revision_id is None:
 
366
            # The null revision is always present.
 
367
            return True
 
368
        path = self.bzrdir._path_for_remote_call(self._client)
 
369
        response = self._client.call('Repository.has_revision', path, revision_id)
 
370
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
371
        return response[0] == 'yes'
 
372
 
 
373
    def has_same_location(self, other):
 
374
        return (self.__class__ == other.__class__ and
 
375
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
376
        
 
377
    def get_graph(self, other_repository=None):
 
378
        """Return the graph for this repository format"""
 
379
        self._ensure_real()
 
380
        return self._real_repository.get_graph(other_repository)
 
381
 
 
382
    def gather_stats(self, revid=None, committers=None):
 
383
        """See Repository.gather_stats()."""
 
384
        path = self.bzrdir._path_for_remote_call(self._client)
 
385
        # revid can be None to indicate no revisions, not just NULL_REVISION
 
386
        if revid is None or revision.is_null(revid):
 
387
            fmt_revid = ''
 
388
        else:
 
389
            fmt_revid = revid
 
390
        if committers is None or not committers:
 
391
            fmt_committers = 'no'
 
392
        else:
 
393
            fmt_committers = 'yes'
 
394
        response = self._client.call_expecting_body(
 
395
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
396
        assert response[0][0] == 'ok', \
 
397
            'unexpected response code %s' % (response[0],)
 
398
 
 
399
        body = response[1].read_body_bytes()
 
400
        result = {}
 
401
        for line in body.split('\n'):
 
402
            if not line:
 
403
                continue
 
404
            key, val_text = line.split(':')
 
405
            if key in ('revisions', 'size', 'committers'):
 
406
                result[key] = int(val_text)
 
407
            elif key in ('firstrev', 'latestrev'):
 
408
                values = val_text.split(' ')[1:]
 
409
                result[key] = (float(values[0]), long(values[1]))
 
410
 
 
411
        return result
 
412
 
 
413
    def get_physical_lock_status(self):
 
414
        """See Repository.get_physical_lock_status()."""
 
415
        # should be an API call to the server.
 
416
        self._ensure_real()
 
417
        return self._real_repository.get_physical_lock_status()
 
418
 
 
419
    def is_in_write_group(self):
 
420
        """Return True if there is an open write group.
 
421
 
 
422
        write groups are only applicable locally for the smart server..
 
423
        """
 
424
        if self._real_repository:
 
425
            return self._real_repository.is_in_write_group()
 
426
 
 
427
    def is_locked(self):
 
428
        return self._lock_count >= 1
 
429
 
 
430
    def is_shared(self):
 
431
        """See Repository.is_shared()."""
 
432
        path = self.bzrdir._path_for_remote_call(self._client)
 
433
        response = self._client.call('Repository.is_shared', path)
 
434
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
435
        return response[0] == 'yes'
 
436
 
 
437
    def is_write_locked(self):
 
438
        return self._lock_mode == 'w'
 
439
 
 
440
    def lock_read(self):
 
441
        # wrong eventually - want a local lock cache context
 
442
        if not self._lock_mode:
 
443
            self._lock_mode = 'r'
 
444
            self._lock_count = 1
 
445
            if self._real_repository is not None:
 
446
                self._real_repository.lock_read()
 
447
        else:
 
448
            self._lock_count += 1
 
449
 
 
450
    def _remote_lock_write(self, token):
 
451
        path = self.bzrdir._path_for_remote_call(self._client)
 
452
        if token is None:
 
453
            token = ''
 
454
        response = self._client.call('Repository.lock_write', path, token)
 
455
        if response[0] == 'ok':
 
456
            ok, token = response
 
457
            return token
 
458
        elif response[0] == 'LockContention':
 
459
            raise errors.LockContention('(remote lock)')
 
460
        elif response[0] == 'UnlockableTransport':
 
461
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
462
        elif response[0] == 'LockFailed':
 
463
            raise errors.LockFailed(response[1], response[2])
 
464
        else:
 
465
            raise errors.UnexpectedSmartServerResponse(response)
 
466
 
 
467
    def lock_write(self, token=None):
 
468
        if not self._lock_mode:
 
469
            self._lock_token = self._remote_lock_write(token)
 
470
            # if self._lock_token is None, then this is something like packs or
 
471
            # svn where we don't get to lock the repo, or a weave style repository
 
472
            # where we cannot lock it over the wire and attempts to do so will
 
473
            # fail.
 
474
            if self._real_repository is not None:
 
475
                self._real_repository.lock_write(token=self._lock_token)
 
476
            if token is not None:
 
477
                self._leave_lock = True
 
478
            else:
 
479
                self._leave_lock = False
 
480
            self._lock_mode = 'w'
 
481
            self._lock_count = 1
 
482
        elif self._lock_mode == 'r':
 
483
            raise errors.ReadOnlyError(self)
 
484
        else:
 
485
            self._lock_count += 1
 
486
        return self._lock_token or None
 
487
 
 
488
    def leave_lock_in_place(self):
 
489
        if not self._lock_token:
 
490
            raise NotImplementedError(self.leave_lock_in_place)
 
491
        self._leave_lock = True
 
492
 
 
493
    def dont_leave_lock_in_place(self):
 
494
        if not self._lock_token:
 
495
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
496
        self._leave_lock = False
 
497
 
 
498
    def _set_real_repository(self, repository):
 
499
        """Set the _real_repository for this repository.
 
500
 
 
501
        :param repository: The repository to fallback to for non-hpss
 
502
            implemented operations.
 
503
        """
 
504
        assert not isinstance(repository, RemoteRepository)
 
505
        self._real_repository = repository
 
506
        if self._lock_mode == 'w':
 
507
            # if we are already locked, the real repository must be able to
 
508
            # acquire the lock with our token.
 
509
            self._real_repository.lock_write(self._lock_token)
 
510
        elif self._lock_mode == 'r':
 
511
            self._real_repository.lock_read()
 
512
 
 
513
    def start_write_group(self):
 
514
        """Start a write group on the decorated repository.
 
515
        
 
516
        Smart methods peform operations in a single step so this api
 
517
        is not really applicable except as a compatibility thunk
 
518
        for older plugins that don't use e.g. the CommitBuilder
 
519
        facility.
 
520
        """
 
521
        self._ensure_real()
 
522
        return self._real_repository.start_write_group()
 
523
 
 
524
    def _unlock(self, token):
 
525
        path = self.bzrdir._path_for_remote_call(self._client)
 
526
        if not token:
 
527
            # with no token the remote repository is not persistently locked.
 
528
            return
 
529
        response = self._client.call('Repository.unlock', path, token)
 
530
        if response == ('ok',):
 
531
            return
 
532
        elif response[0] == 'TokenMismatch':
 
533
            raise errors.TokenMismatch(token, '(remote token)')
 
534
        else:
 
535
            raise errors.UnexpectedSmartServerResponse(response)
 
536
 
 
537
    def unlock(self):
 
538
        self._lock_count -= 1
 
539
        if self._lock_count > 0:
 
540
            return
 
541
        old_mode = self._lock_mode
 
542
        self._lock_mode = None
 
543
        try:
 
544
            # The real repository is responsible at present for raising an
 
545
            # exception if it's in an unfinished write group.  However, it
 
546
            # normally will *not* actually remove the lock from disk - that's
 
547
            # done by the server on receiving the Repository.unlock call.
 
548
            # This is just to let the _real_repository stay up to date.
 
549
            if self._real_repository is not None:
 
550
                self._real_repository.unlock()
 
551
        finally:
 
552
            # The rpc-level lock should be released even if there was a
 
553
            # problem releasing the vfs-based lock.
 
554
            if old_mode == 'w':
 
555
                # Only write-locked repositories need to make a remote method
 
556
                # call to perfom the unlock.
 
557
                old_token = self._lock_token
 
558
                self._lock_token = None
 
559
                if not self._leave_lock:
 
560
                    self._unlock(old_token)
 
561
 
 
562
    def break_lock(self):
 
563
        # should hand off to the network
 
564
        self._ensure_real()
 
565
        return self._real_repository.break_lock()
 
566
 
 
567
    def _get_tarball(self, compression):
 
568
        """Return a TemporaryFile containing a repository tarball.
 
569
        
 
570
        Returns None if the server does not support sending tarballs.
 
571
        """
 
572
        import tempfile
 
573
        path = self.bzrdir._path_for_remote_call(self._client)
 
574
        response, protocol = self._client.call_expecting_body(
 
575
            'Repository.tarball', path, compression)
 
576
        if response[0] == 'ok':
 
577
            # Extract the tarball and return it
 
578
            t = tempfile.NamedTemporaryFile()
 
579
            # TODO: rpc layer should read directly into it...
 
580
            t.write(protocol.read_body_bytes())
 
581
            t.seek(0)
 
582
            return t
 
583
        if (response == ('error', "Generic bzr smart protocol error: "
 
584
                "bad request 'Repository.tarball'") or
 
585
              response == ('error', "Generic bzr smart protocol error: "
 
586
                "bad request u'Repository.tarball'")):
 
587
            protocol.cancel_read_body()
 
588
            return None
 
589
        raise errors.UnexpectedSmartServerResponse(response)
 
590
 
 
591
    def sprout(self, to_bzrdir, revision_id=None):
 
592
        # TODO: Option to control what format is created?
 
593
        dest_repo = to_bzrdir.create_repository()
 
594
        dest_repo.fetch(self, revision_id=revision_id)
 
595
        return dest_repo
 
596
 
 
597
    ### These methods are just thin shims to the VFS object for now.
 
598
 
 
599
    def revision_tree(self, revision_id):
 
600
        self._ensure_real()
 
601
        return self._real_repository.revision_tree(revision_id)
 
602
 
 
603
    def get_serializer_format(self):
 
604
        self._ensure_real()
 
605
        return self._real_repository.get_serializer_format()
 
606
 
 
607
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
608
                           timezone=None, committer=None, revprops=None,
 
609
                           revision_id=None):
 
610
        # FIXME: It ought to be possible to call this without immediately
 
611
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
612
        self._ensure_real()
 
613
        builder = self._real_repository.get_commit_builder(branch, parents,
 
614
                config, timestamp=timestamp, timezone=timezone,
 
615
                committer=committer, revprops=revprops, revision_id=revision_id)
 
616
        return builder
 
617
 
 
618
    @needs_write_lock
 
619
    def add_inventory(self, revid, inv, parents):
 
620
        self._ensure_real()
 
621
        return self._real_repository.add_inventory(revid, inv, parents)
 
622
 
 
623
    @needs_write_lock
 
624
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
625
        self._ensure_real()
 
626
        return self._real_repository.add_revision(
 
627
            rev_id, rev, inv=inv, config=config)
 
628
 
 
629
    @needs_read_lock
 
630
    def get_inventory(self, revision_id):
 
631
        self._ensure_real()
 
632
        return self._real_repository.get_inventory(revision_id)
 
633
 
 
634
    @needs_read_lock
 
635
    def get_revision(self, revision_id):
 
636
        self._ensure_real()
 
637
        return self._real_repository.get_revision(revision_id)
 
638
 
 
639
    @property
 
640
    def weave_store(self):
 
641
        self._ensure_real()
 
642
        return self._real_repository.weave_store
 
643
 
 
644
    def get_transaction(self):
 
645
        self._ensure_real()
 
646
        return self._real_repository.get_transaction()
 
647
 
 
648
    @needs_read_lock
 
649
    def clone(self, a_bzrdir, revision_id=None):
 
650
        self._ensure_real()
 
651
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
652
 
 
653
    def make_working_trees(self):
 
654
        """RemoteRepositories never create working trees by default."""
 
655
        return False
 
656
 
 
657
    def fetch(self, source, revision_id=None, pb=None):
 
658
        if self.has_same_location(source):
 
659
            # check that last_revision is in 'from' and then return a
 
660
            # no-operation.
 
661
            if (revision_id is not None and
 
662
                not revision.is_null(revision_id)):
 
663
                self.get_revision(revision_id)
 
664
            return 0, []
 
665
        self._ensure_real()
 
666
        return self._real_repository.fetch(
 
667
            source, revision_id=revision_id, pb=pb)
 
668
 
 
669
    def create_bundle(self, target, base, fileobj, format=None):
 
670
        self._ensure_real()
 
671
        self._real_repository.create_bundle(target, base, fileobj, format)
 
672
 
 
673
    @property
 
674
    def control_weaves(self):
 
675
        self._ensure_real()
 
676
        return self._real_repository.control_weaves
 
677
 
 
678
    @needs_read_lock
 
679
    def get_ancestry(self, revision_id, topo_sorted=True):
 
680
        self._ensure_real()
 
681
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
682
 
 
683
    @needs_read_lock
 
684
    def get_inventory_weave(self):
 
685
        self._ensure_real()
 
686
        return self._real_repository.get_inventory_weave()
 
687
 
 
688
    def fileids_altered_by_revision_ids(self, revision_ids):
 
689
        self._ensure_real()
 
690
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
691
 
 
692
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
 
693
        self._ensure_real()
 
694
        return self._real_repository._get_versioned_file_checker(
 
695
            revisions, revision_versions_cache)
 
696
        
 
697
    def iter_files_bytes(self, desired_files):
 
698
        """See Repository.iter_file_bytes.
 
699
        """
 
700
        self._ensure_real()
 
701
        return self._real_repository.iter_files_bytes(desired_files)
 
702
 
 
703
    @needs_read_lock
 
704
    def get_signature_text(self, revision_id):
 
705
        self._ensure_real()
 
706
        return self._real_repository.get_signature_text(revision_id)
 
707
 
 
708
    @needs_read_lock
 
709
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
710
        self._ensure_real()
 
711
        return self._real_repository.get_revision_graph_with_ghosts(
 
712
            revision_ids=revision_ids)
 
713
 
 
714
    @needs_read_lock
 
715
    def get_inventory_xml(self, revision_id):
 
716
        self._ensure_real()
 
717
        return self._real_repository.get_inventory_xml(revision_id)
 
718
 
 
719
    def deserialise_inventory(self, revision_id, xml):
 
720
        self._ensure_real()
 
721
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
722
 
 
723
    def reconcile(self, other=None, thorough=False):
 
724
        self._ensure_real()
 
725
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
726
        
 
727
    def all_revision_ids(self):
 
728
        self._ensure_real()
 
729
        return self._real_repository.all_revision_ids()
 
730
    
 
731
    @needs_read_lock
 
732
    def get_deltas_for_revisions(self, revisions):
 
733
        self._ensure_real()
 
734
        return self._real_repository.get_deltas_for_revisions(revisions)
 
735
 
 
736
    @needs_read_lock
 
737
    def get_revision_delta(self, revision_id):
 
738
        self._ensure_real()
 
739
        return self._real_repository.get_revision_delta(revision_id)
 
740
 
 
741
    @needs_read_lock
 
742
    def revision_trees(self, revision_ids):
 
743
        self._ensure_real()
 
744
        return self._real_repository.revision_trees(revision_ids)
 
745
 
 
746
    @needs_read_lock
 
747
    def get_revision_reconcile(self, revision_id):
 
748
        self._ensure_real()
 
749
        return self._real_repository.get_revision_reconcile(revision_id)
 
750
 
 
751
    @needs_read_lock
 
752
    def check(self, revision_ids=None):
 
753
        self._ensure_real()
 
754
        return self._real_repository.check(revision_ids=revision_ids)
 
755
 
 
756
    def copy_content_into(self, destination, revision_id=None):
 
757
        self._ensure_real()
 
758
        return self._real_repository.copy_content_into(
 
759
            destination, revision_id=revision_id)
 
760
 
 
761
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
762
        # get a tarball of the remote repository, and copy from that into the
 
763
        # destination
 
764
        from bzrlib import osutils
 
765
        import tarfile
 
766
        import tempfile
 
767
        from StringIO import StringIO
 
768
        # TODO: Maybe a progress bar while streaming the tarball?
 
769
        note("Copying repository content as tarball...")
 
770
        tar_file = self._get_tarball('bz2')
 
771
        if tar_file is None:
 
772
            return None
 
773
        destination = to_bzrdir.create_repository()
 
774
        try:
 
775
            tar = tarfile.open('repository', fileobj=tar_file,
 
776
                mode='r|bz2')
 
777
            tmpdir = tempfile.mkdtemp()
 
778
            try:
 
779
                _extract_tar(tar, tmpdir)
 
780
                tmp_bzrdir = BzrDir.open(tmpdir)
 
781
                tmp_repo = tmp_bzrdir.open_repository()
 
782
                tmp_repo.copy_content_into(destination, revision_id)
 
783
            finally:
 
784
                osutils.rmtree(tmpdir)
 
785
        finally:
 
786
            tar_file.close()
 
787
        return destination
 
788
        # TODO: Suggestion from john: using external tar is much faster than
 
789
        # python's tarfile library, but it may not work on windows.
 
790
 
 
791
    @needs_write_lock
 
792
    def pack(self):
 
793
        """Compress the data within the repository.
 
794
 
 
795
        This is not currently implemented within the smart server.
 
796
        """
 
797
        self._ensure_real()
 
798
        return self._real_repository.pack()
 
799
 
 
800
    def set_make_working_trees(self, new_value):
 
801
        raise NotImplementedError(self.set_make_working_trees)
 
802
 
 
803
    @needs_write_lock
 
804
    def sign_revision(self, revision_id, gpg_strategy):
 
805
        self._ensure_real()
 
806
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
807
 
 
808
    @needs_read_lock
 
809
    def get_revisions(self, revision_ids):
 
810
        self._ensure_real()
 
811
        return self._real_repository.get_revisions(revision_ids)
 
812
 
 
813
    def supports_rich_root(self):
 
814
        self._ensure_real()
 
815
        return self._real_repository.supports_rich_root()
 
816
 
 
817
    def iter_reverse_revision_history(self, revision_id):
 
818
        self._ensure_real()
 
819
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
820
 
 
821
    @property
 
822
    def _serializer(self):
 
823
        self._ensure_real()
 
824
        return self._real_repository._serializer
 
825
 
 
826
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
827
        self._ensure_real()
 
828
        return self._real_repository.store_revision_signature(
 
829
            gpg_strategy, plaintext, revision_id)
 
830
 
 
831
    def add_signature_text(self, revision_id, signature):
 
832
        self._ensure_real()
 
833
        return self._real_repository.add_signature_text(revision_id, signature)
 
834
 
 
835
    def has_signature_for_revision_id(self, revision_id):
 
836
        self._ensure_real()
 
837
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
838
 
 
839
    def get_data_stream(self, revision_ids):
 
840
        path = self.bzrdir._path_for_remote_call(self._client)
 
841
        response, protocol = self._client.call_expecting_body(
 
842
            'Repository.stream_knit_data_for_revisions', path, *revision_ids)
 
843
        if response == ('ok',):
 
844
            return self._deserialise_stream(protocol)
 
845
        elif (response == ('error', "Generic bzr smart protocol error: "
 
846
                "bad request 'Repository.stream_knit_data_for_revisions'") or
 
847
              response == ('error', "Generic bzr smart protocol error: "
 
848
                "bad request u'Repository.stream_knit_data_for_revisions'")):
 
849
            protocol.cancel_read_body()
 
850
            self._ensure_real()
 
851
            return self._real_repository.get_data_stream(revision_ids)
 
852
        else:
 
853
            raise errors.UnexpectedSmartServerResponse(response)
 
854
 
 
855
    def _deserialise_stream(self, protocol):
 
856
        buffer = StringIO(protocol.read_body_bytes())
 
857
        reader = ContainerReader(buffer)
 
858
        for record_names, read_bytes in reader.iter_records():
 
859
            try:
 
860
                # These records should have only one name, and that name
 
861
                # should be a one-element tuple.
 
862
                [name_tuple] = record_names
 
863
            except ValueError:
 
864
                raise errors.SmartProtocolError(
 
865
                    'Repository data stream had invalid record name %r'
 
866
                    % (record_names,))
 
867
            yield name_tuple, read_bytes(None)
 
868
 
 
869
    def insert_data_stream(self, stream):
 
870
        self._ensure_real()
 
871
        self._real_repository.insert_data_stream(stream)
 
872
 
 
873
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
874
        self._ensure_real()
 
875
        return self._real_repository.item_keys_introduced_by(revision_ids,
 
876
            _files_pb=_files_pb)
 
877
 
 
878
    def revision_graph_can_have_wrong_parents(self):
 
879
        # The answer depends on the remote repo format.
 
880
        self._ensure_real()
 
881
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
882
 
 
883
    def _find_inconsistent_revision_parents(self):
 
884
        self._ensure_real()
 
885
        return self._real_repository._find_inconsistent_revision_parents()
 
886
 
 
887
    def _check_for_inconsistent_revision_parents(self):
 
888
        self._ensure_real()
 
889
        return self._real_repository._check_for_inconsistent_revision_parents()
 
890
 
 
891
 
 
892
class RemoteBranchLockableFiles(LockableFiles):
 
893
    """A 'LockableFiles' implementation that talks to a smart server.
 
894
    
 
895
    This is not a public interface class.
 
896
    """
 
897
 
 
898
    def __init__(self, bzrdir, _client):
 
899
        self.bzrdir = bzrdir
 
900
        self._client = _client
 
901
        self._need_find_modes = True
 
902
        LockableFiles.__init__(
 
903
            self, bzrdir.get_branch_transport(None),
 
904
            'lock', lockdir.LockDir)
 
905
 
 
906
    def _find_modes(self):
 
907
        # RemoteBranches don't let the client set the mode of control files.
 
908
        self._dir_mode = None
 
909
        self._file_mode = None
 
910
 
 
911
    def get(self, path):
 
912
        """'get' a remote path as per the LockableFiles interface.
 
913
 
 
914
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
915
             just retrieve a file, instead we ask the smart server to generate
 
916
             a configuration for us - which is retrieved as an INI file.
 
917
        """
 
918
        if path == 'branch.conf':
 
919
            path = self.bzrdir._path_for_remote_call(self._client)
 
920
            response = self._client.call_expecting_body(
 
921
                'Branch.get_config_file', path)
 
922
            assert response[0][0] == 'ok', \
 
923
                'unexpected response code %s' % (response[0],)
 
924
            return StringIO(response[1].read_body_bytes())
 
925
        else:
 
926
            # VFS fallback.
 
927
            return LockableFiles.get(self, path)
 
928
 
 
929
 
 
930
class RemoteBranchFormat(branch.BranchFormat):
 
931
 
 
932
    def __eq__(self, other):
 
933
        return (isinstance(other, RemoteBranchFormat) and 
 
934
            self.__dict__ == other.__dict__)
 
935
 
 
936
    def get_format_description(self):
 
937
        return 'Remote BZR Branch'
 
938
 
 
939
    def get_format_string(self):
 
940
        return 'Remote BZR Branch'
 
941
 
 
942
    def open(self, a_bzrdir):
 
943
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
944
        return a_bzrdir.open_branch()
 
945
 
 
946
    def initialize(self, a_bzrdir):
 
947
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
948
        return a_bzrdir.create_branch()
 
949
 
 
950
    def supports_tags(self):
 
951
        # Remote branches might support tags, but we won't know until we
 
952
        # access the real remote branch.
 
953
        return True
 
954
 
 
955
 
 
956
class RemoteBranch(branch.Branch):
 
957
    """Branch stored on a server accessed by HPSS RPC.
 
958
 
 
959
    At the moment most operations are mapped down to simple file operations.
 
960
    """
 
961
 
 
962
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
963
        _client=None):
 
964
        """Create a RemoteBranch instance.
 
965
 
 
966
        :param real_branch: An optional local implementation of the branch
 
967
            format, usually accessing the data via the VFS.
 
968
        :param _client: Private parameter for testing.
 
969
        """
 
970
        # We intentionally don't call the parent class's __init__, because it
 
971
        # will try to assign to self.tags, which is a property in this subclass.
 
972
        # And the parent's __init__ doesn't do much anyway.
 
973
        self._revision_id_to_revno_cache = None
 
974
        self._revision_history_cache = None
 
975
        self.bzrdir = remote_bzrdir
 
976
        if _client is not None:
 
977
            self._client = _client
 
978
        else:
 
979
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
980
        self.repository = remote_repository
 
981
        if real_branch is not None:
 
982
            self._real_branch = real_branch
 
983
            # Give the remote repository the matching real repo.
 
984
            real_repo = self._real_branch.repository
 
985
            if isinstance(real_repo, RemoteRepository):
 
986
                real_repo._ensure_real()
 
987
                real_repo = real_repo._real_repository
 
988
            self.repository._set_real_repository(real_repo)
 
989
            # Give the branch the remote repository to let fast-pathing happen.
 
990
            self._real_branch.repository = self.repository
 
991
        else:
 
992
            self._real_branch = None
 
993
        # Fill out expected attributes of branch for bzrlib api users.
 
994
        self._format = RemoteBranchFormat()
 
995
        self.base = self.bzrdir.root_transport.base
 
996
        self._control_files = None
 
997
        self._lock_mode = None
 
998
        self._lock_token = None
 
999
        self._lock_count = 0
 
1000
        self._leave_lock = False
 
1001
 
 
1002
    def __str__(self):
 
1003
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
1004
 
 
1005
    __repr__ = __str__
 
1006
 
 
1007
    def _ensure_real(self):
 
1008
        """Ensure that there is a _real_branch set.
 
1009
 
 
1010
        Used before calls to self._real_branch.
 
1011
        """
 
1012
        if not self._real_branch:
 
1013
            assert vfs.vfs_enabled()
 
1014
            self.bzrdir._ensure_real()
 
1015
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
1016
            # Give the remote repository the matching real repo.
 
1017
            real_repo = self._real_branch.repository
 
1018
            if isinstance(real_repo, RemoteRepository):
 
1019
                real_repo._ensure_real()
 
1020
                real_repo = real_repo._real_repository
 
1021
            self.repository._set_real_repository(real_repo)
 
1022
            # Give the branch the remote repository to let fast-pathing happen.
 
1023
            self._real_branch.repository = self.repository
 
1024
            # XXX: deal with _lock_mode == 'w'
 
1025
            if self._lock_mode == 'r':
 
1026
                self._real_branch.lock_read()
 
1027
 
 
1028
    @property
 
1029
    def control_files(self):
 
1030
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
1031
        # because it triggers an _ensure_real that we otherwise might not need.
 
1032
        if self._control_files is None:
 
1033
            self._control_files = RemoteBranchLockableFiles(
 
1034
                self.bzrdir, self._client)
 
1035
        return self._control_files
 
1036
 
 
1037
    def _get_checkout_format(self):
 
1038
        self._ensure_real()
 
1039
        return self._real_branch._get_checkout_format()
 
1040
 
 
1041
    def get_physical_lock_status(self):
 
1042
        """See Branch.get_physical_lock_status()."""
 
1043
        # should be an API call to the server, as branches must be lockable.
 
1044
        self._ensure_real()
 
1045
        return self._real_branch.get_physical_lock_status()
 
1046
 
 
1047
    def lock_read(self):
 
1048
        if not self._lock_mode:
 
1049
            self._lock_mode = 'r'
 
1050
            self._lock_count = 1
 
1051
            if self._real_branch is not None:
 
1052
                self._real_branch.lock_read()
 
1053
        else:
 
1054
            self._lock_count += 1
 
1055
 
 
1056
    def _remote_lock_write(self, token):
 
1057
        if token is None:
 
1058
            branch_token = repo_token = ''
 
1059
        else:
 
1060
            branch_token = token
 
1061
            repo_token = self.repository.lock_write()
 
1062
            self.repository.unlock()
 
1063
        path = self.bzrdir._path_for_remote_call(self._client)
 
1064
        response = self._client.call('Branch.lock_write', path, branch_token,
 
1065
                                     repo_token or '')
 
1066
        if response[0] == 'ok':
 
1067
            ok, branch_token, repo_token = response
 
1068
            return branch_token, repo_token
 
1069
        elif response[0] == 'LockContention':
 
1070
            raise errors.LockContention('(remote lock)')
 
1071
        elif response[0] == 'TokenMismatch':
 
1072
            raise errors.TokenMismatch(token, '(remote token)')
 
1073
        elif response[0] == 'UnlockableTransport':
 
1074
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
1075
        elif response[0] == 'ReadOnlyError':
 
1076
            raise errors.ReadOnlyError(self)
 
1077
        elif response[0] == 'LockFailed':
 
1078
            raise errors.LockFailed(response[1], response[2])
 
1079
        else:
 
1080
            raise errors.UnexpectedSmartServerResponse(response)
 
1081
            
 
1082
    def lock_write(self, token=None):
 
1083
        if not self._lock_mode:
 
1084
            remote_tokens = self._remote_lock_write(token)
 
1085
            self._lock_token, self._repo_lock_token = remote_tokens
 
1086
            assert self._lock_token, 'Remote server did not return a token!'
 
1087
            # TODO: We really, really, really don't want to call _ensure_real
 
1088
            # here, but it's the easiest way to ensure coherency between the
 
1089
            # state of the RemoteBranch and RemoteRepository objects and the
 
1090
            # physical locks.  If we don't materialise the real objects here,
 
1091
            # then getting everything in the right state later is complex, so
 
1092
            # for now we just do it the lazy way.
 
1093
            #   -- Andrew Bennetts, 2007-02-22.
 
1094
            self._ensure_real()
 
1095
            if self._real_branch is not None:
 
1096
                self._real_branch.repository.lock_write(
 
1097
                    token=self._repo_lock_token)
 
1098
                try:
 
1099
                    self._real_branch.lock_write(token=self._lock_token)
 
1100
                finally:
 
1101
                    self._real_branch.repository.unlock()
 
1102
            if token is not None:
 
1103
                self._leave_lock = True
 
1104
            else:
 
1105
                # XXX: this case seems to be unreachable; token cannot be None.
 
1106
                self._leave_lock = False
 
1107
            self._lock_mode = 'w'
 
1108
            self._lock_count = 1
 
1109
        elif self._lock_mode == 'r':
 
1110
            raise errors.ReadOnlyTransaction
 
1111
        else:
 
1112
            if token is not None:
 
1113
                # A token was given to lock_write, and we're relocking, so check
 
1114
                # that the given token actually matches the one we already have.
 
1115
                if token != self._lock_token:
 
1116
                    raise errors.TokenMismatch(token, self._lock_token)
 
1117
            self._lock_count += 1
 
1118
        return self._lock_token or None
 
1119
 
 
1120
    def _unlock(self, branch_token, repo_token):
 
1121
        path = self.bzrdir._path_for_remote_call(self._client)
 
1122
        response = self._client.call('Branch.unlock', path, branch_token,
 
1123
                                     repo_token or '')
 
1124
        if response == ('ok',):
 
1125
            return
 
1126
        elif response[0] == 'TokenMismatch':
 
1127
            raise errors.TokenMismatch(
 
1128
                str((branch_token, repo_token)), '(remote tokens)')
 
1129
        else:
 
1130
            raise errors.UnexpectedSmartServerResponse(response)
 
1131
 
 
1132
    def unlock(self):
 
1133
        self._lock_count -= 1
 
1134
        if not self._lock_count:
 
1135
            self._clear_cached_state()
 
1136
            mode = self._lock_mode
 
1137
            self._lock_mode = None
 
1138
            if self._real_branch is not None:
 
1139
                if (not self._leave_lock and mode == 'w' and
 
1140
                    self._repo_lock_token):
 
1141
                    # If this RemoteBranch will remove the physical lock for the
 
1142
                    # repository, make sure the _real_branch doesn't do it
 
1143
                    # first.  (Because the _real_branch's repository is set to
 
1144
                    # be the RemoteRepository.)
 
1145
                    self._real_branch.repository.leave_lock_in_place()
 
1146
                self._real_branch.unlock()
 
1147
            if mode != 'w':
 
1148
                # Only write-locked branched need to make a remote method call
 
1149
                # to perfom the unlock.
 
1150
                return
 
1151
            assert self._lock_token, 'Locked, but no token!'
 
1152
            branch_token = self._lock_token
 
1153
            repo_token = self._repo_lock_token
 
1154
            self._lock_token = None
 
1155
            self._repo_lock_token = None
 
1156
            if not self._leave_lock:
 
1157
                self._unlock(branch_token, repo_token)
 
1158
 
 
1159
    def break_lock(self):
 
1160
        self._ensure_real()
 
1161
        return self._real_branch.break_lock()
 
1162
 
 
1163
    def leave_lock_in_place(self):
 
1164
        if not self._lock_token:
 
1165
            raise NotImplementedError(self.leave_lock_in_place)
 
1166
        self._leave_lock = True
 
1167
 
 
1168
    def dont_leave_lock_in_place(self):
 
1169
        if not self._lock_token:
 
1170
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1171
        self._leave_lock = False
 
1172
 
 
1173
    def last_revision_info(self):
 
1174
        """See Branch.last_revision_info()."""
 
1175
        path = self.bzrdir._path_for_remote_call(self._client)
 
1176
        response = self._client.call('Branch.last_revision_info', path)
 
1177
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
1178
        revno = int(response[1])
 
1179
        last_revision = response[2]
 
1180
        return (revno, last_revision)
 
1181
 
 
1182
    def _gen_revision_history(self):
 
1183
        """See Branch._gen_revision_history()."""
 
1184
        path = self.bzrdir._path_for_remote_call(self._client)
 
1185
        response = self._client.call_expecting_body(
 
1186
            'Branch.revision_history', path)
 
1187
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1188
                                        % (response[0],))
 
1189
        result = response[1].read_body_bytes().split('\x00')
 
1190
        if result == ['']:
 
1191
            return []
 
1192
        return result
 
1193
 
 
1194
    @needs_write_lock
 
1195
    def set_revision_history(self, rev_history):
 
1196
        # Send just the tip revision of the history; the server will generate
 
1197
        # the full history from that.  If the revision doesn't exist in this
 
1198
        # branch, NoSuchRevision will be raised.
 
1199
        path = self.bzrdir._path_for_remote_call(self._client)
 
1200
        if rev_history == []:
 
1201
            rev_id = 'null:'
 
1202
        else:
 
1203
            rev_id = rev_history[-1]
 
1204
        self._clear_cached_state()
 
1205
        response = self._client.call('Branch.set_last_revision',
 
1206
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1207
        if response[0] == 'NoSuchRevision':
 
1208
            raise NoSuchRevision(self, rev_id)
 
1209
        else:
 
1210
            assert response == ('ok',), (
 
1211
                'unexpected response code %r' % (response,))
 
1212
        self._cache_revision_history(rev_history)
 
1213
 
 
1214
    def get_parent(self):
 
1215
        self._ensure_real()
 
1216
        return self._real_branch.get_parent()
 
1217
        
 
1218
    def set_parent(self, url):
 
1219
        self._ensure_real()
 
1220
        return self._real_branch.set_parent(url)
 
1221
        
 
1222
    def get_config(self):
 
1223
        return RemoteBranchConfig(self)
 
1224
 
 
1225
    def sprout(self, to_bzrdir, revision_id=None):
 
1226
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1227
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1228
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1229
        # to_bzrdir.create_branch...
 
1230
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
1231
        self.copy_content_into(result, revision_id=revision_id)
 
1232
        result.set_parent(self.bzrdir.root_transport.base)
 
1233
        return result
 
1234
 
 
1235
    @needs_write_lock
 
1236
    def pull(self, source, overwrite=False, stop_revision=None,
 
1237
             **kwargs):
 
1238
        # FIXME: This asks the real branch to run the hooks, which means
 
1239
        # they're called with the wrong target branch parameter. 
 
1240
        # The test suite specifically allows this at present but it should be
 
1241
        # fixed.  It should get a _override_hook_target branch,
 
1242
        # as push does.  -- mbp 20070405
 
1243
        self._ensure_real()
 
1244
        self._real_branch.pull(
 
1245
            source, overwrite=overwrite, stop_revision=stop_revision,
 
1246
            **kwargs)
 
1247
 
 
1248
    @needs_read_lock
 
1249
    def push(self, target, overwrite=False, stop_revision=None):
 
1250
        self._ensure_real()
 
1251
        return self._real_branch.push(
 
1252
            target, overwrite=overwrite, stop_revision=stop_revision,
 
1253
            _override_hook_source_branch=self)
 
1254
 
 
1255
    def is_locked(self):
 
1256
        return self._lock_count >= 1
 
1257
 
 
1258
    def set_last_revision_info(self, revno, revision_id):
 
1259
        self._ensure_real()
 
1260
        self._clear_cached_state()
 
1261
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
1262
 
 
1263
    def generate_revision_history(self, revision_id, last_rev=None,
 
1264
                                  other_branch=None):
 
1265
        self._ensure_real()
 
1266
        return self._real_branch.generate_revision_history(
 
1267
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
1268
 
 
1269
    @property
 
1270
    def tags(self):
 
1271
        self._ensure_real()
 
1272
        return self._real_branch.tags
 
1273
 
 
1274
    def set_push_location(self, location):
 
1275
        self._ensure_real()
 
1276
        return self._real_branch.set_push_location(location)
 
1277
 
 
1278
    def update_revisions(self, other, stop_revision=None):
 
1279
        self._ensure_real()
 
1280
        return self._real_branch.update_revisions(
 
1281
            other, stop_revision=stop_revision)
 
1282
 
 
1283
 
 
1284
class RemoteBranchConfig(BranchConfig):
 
1285
 
 
1286
    def username(self):
 
1287
        self.branch._ensure_real()
 
1288
        return self.branch._real_branch.get_config().username()
 
1289
 
 
1290
    def _get_branch_data_config(self):
 
1291
        self.branch._ensure_real()
 
1292
        if self._branch_data_config is None:
 
1293
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1294
        return self._branch_data_config
 
1295
 
 
1296
 
 
1297
def _extract_tar(tar, to_dir):
 
1298
    """Extract all the contents of a tarfile object.
 
1299
 
 
1300
    A replacement for extractall, which is not present in python2.4
 
1301
    """
 
1302
    for tarinfo in tar:
 
1303
        tar.extract(tarinfo, to_dir)