/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Andrew Bennetts
  • Date: 2008-01-14 22:45:15 UTC
  • mfrom: (3179 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3180.
  • Revision ID: andrew.bennetts@canonical.com-20080114224515-izp51fxci3hhopap
Merge from bzr.dev.

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