/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-01-11 07:06:36 UTC
  • mfrom: (3172.2.1 remote-fetch-formats)
  • Revision ID: pqm@pqm.ubuntu.com-20080111070636-jhozu5eo7wvh1k0o
(andrew) Enable use of smart revision streaming between repos with
        compatible models.

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