/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

Add simple tests and docstrings for GraphWalker.

Show diffs side-by-side

added added

removed removed

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