/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: Robert Collins
  • Date: 2007-10-15 07:56:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2908.
  • Revision ID: robertc@robertcollins.net-20071015075604-ouspx8c4xwe04llz
Review feedback.

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