/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Andrew Bennetts
  • Date: 2007-10-05 02:18:53 UTC
  • mto: This revision was merged to the branch mainline in revision 2905.
  • Revision ID: andrew.bennetts@canonical.com-20071005021853-q410n3kcldnnp2ms
Revert bad change to bzrlib/progress.py

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._reconcile_fixes_text_parents = True
 
263
        self.base = self.bzrdir.transport.base
 
264
 
 
265
    def __str__(self):
 
266
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
267
 
 
268
    __repr__ = __str__
 
269
 
 
270
    def abort_write_group(self):
 
271
        """Complete a write group on the decorated repository.
 
272
        
 
273
        Smart methods peform operations in a single step so this api
 
274
        is not really applicable except as a compatibility thunk
 
275
        for older plugins that don't use e.g. the CommitBuilder
 
276
        facility.
 
277
        """
 
278
        self._ensure_real()
 
279
        return self._real_repository.abort_write_group()
 
280
 
 
281
    def commit_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.commit_write_group()
 
291
 
 
292
    def _ensure_real(self):
 
293
        """Ensure that there is a _real_repository set.
 
294
 
 
295
        Used before calls to self._real_repository.
 
296
        """
 
297
        if not self._real_repository:
 
298
            self.bzrdir._ensure_real()
 
299
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
300
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
301
 
 
302
    def get_revision_graph(self, revision_id=None):
 
303
        """See Repository.get_revision_graph()."""
 
304
        if revision_id is None:
 
305
            revision_id = ''
 
306
        elif revision_id == NULL_REVISION:
 
307
            return {}
 
308
 
 
309
        path = self.bzrdir._path_for_remote_call(self._client)
 
310
        assert type(revision_id) is str
 
311
        response = self._client.call_expecting_body(
 
312
            'Repository.get_revision_graph', path, revision_id)
 
313
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
314
            raise errors.UnexpectedSmartServerResponse(response[0])
 
315
        if response[0][0] == 'ok':
 
316
            coded = response[1].read_body_bytes()
 
317
            if coded == '':
 
318
                # no revisions in this repository!
 
319
                return {}
 
320
            lines = coded.split('\n')
 
321
            revision_graph = {}
 
322
            for line in lines:
 
323
                d = tuple(line.split())
 
324
                revision_graph[d[0]] = d[1:]
 
325
                
 
326
            return revision_graph
 
327
        else:
 
328
            response_body = response[1].read_body_bytes()
 
329
            assert response_body == ''
 
330
            raise NoSuchRevision(self, revision_id)
 
331
 
 
332
    def has_revision(self, revision_id):
 
333
        """See Repository.has_revision()."""
 
334
        if revision_id is None:
 
335
            # The null revision is always present.
 
336
            return True
 
337
        path = self.bzrdir._path_for_remote_call(self._client)
 
338
        response = self._client.call('Repository.has_revision', path, revision_id)
 
339
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
340
        return response[0] == 'yes'
 
341
 
 
342
    def has_same_location(self, other):
 
343
        return (self.__class__ == other.__class__ and
 
344
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
345
        
 
346
    def get_graph(self, other_repository=None):
 
347
        """Return the graph for this repository format"""
 
348
        self._ensure_real()
 
349
        return self._real_repository.get_graph(other_repository)
 
350
 
 
351
    def gather_stats(self, revid=None, committers=None):
 
352
        """See Repository.gather_stats()."""
 
353
        path = self.bzrdir._path_for_remote_call(self._client)
 
354
        if revid in (None, NULL_REVISION):
 
355
            fmt_revid = ''
 
356
        else:
 
357
            fmt_revid = revid
 
358
        if committers is None or not committers:
 
359
            fmt_committers = 'no'
 
360
        else:
 
361
            fmt_committers = 'yes'
 
362
        response = self._client.call_expecting_body(
 
363
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
364
        assert response[0][0] == 'ok', \
 
365
            'unexpected response code %s' % (response[0],)
 
366
 
 
367
        body = response[1].read_body_bytes()
 
368
        result = {}
 
369
        for line in body.split('\n'):
 
370
            if not line:
 
371
                continue
 
372
            key, val_text = line.split(':')
 
373
            if key in ('revisions', 'size', 'committers'):
 
374
                result[key] = int(val_text)
 
375
            elif key in ('firstrev', 'latestrev'):
 
376
                values = val_text.split(' ')[1:]
 
377
                result[key] = (float(values[0]), long(values[1]))
 
378
 
 
379
        return result
 
380
 
 
381
    def get_physical_lock_status(self):
 
382
        """See Repository.get_physical_lock_status()."""
 
383
        return False
 
384
 
 
385
    def is_in_write_group(self):
 
386
        """Return True if there is an open write group.
 
387
 
 
388
        write groups are only applicable locally for the smart server..
 
389
        """
 
390
        if self._real_repository:
 
391
            return self._real_repository.is_in_write_group()
 
392
 
 
393
    def is_locked(self):
 
394
        return self._lock_count >= 1
 
395
 
 
396
    def is_shared(self):
 
397
        """See Repository.is_shared()."""
 
398
        path = self.bzrdir._path_for_remote_call(self._client)
 
399
        response = self._client.call('Repository.is_shared', path)
 
400
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
401
        return response[0] == 'yes'
 
402
 
 
403
    def lock_read(self):
 
404
        # wrong eventually - want a local lock cache context
 
405
        if not self._lock_mode:
 
406
            self._lock_mode = 'r'
 
407
            self._lock_count = 1
 
408
            if self._real_repository is not None:
 
409
                self._real_repository.lock_read()
 
410
        else:
 
411
            self._lock_count += 1
 
412
 
 
413
    def _remote_lock_write(self, token):
 
414
        path = self.bzrdir._path_for_remote_call(self._client)
 
415
        if token is None:
 
416
            token = ''
 
417
        response = self._client.call('Repository.lock_write', path, token)
 
418
        if response[0] == 'ok':
 
419
            ok, token = response
 
420
            return token
 
421
        elif response[0] == 'LockContention':
 
422
            raise errors.LockContention('(remote lock)')
 
423
        elif response[0] == 'UnlockableTransport':
 
424
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
425
        else:
 
426
            raise errors.UnexpectedSmartServerResponse(response)
 
427
 
 
428
    def lock_write(self, token=None):
 
429
        if not self._lock_mode:
 
430
            self._lock_token = self._remote_lock_write(token)
 
431
            assert self._lock_token, 'Remote server did not return a token!'
 
432
            if self._real_repository is not None:
 
433
                self._real_repository.lock_write(token=self._lock_token)
 
434
            if token is not None:
 
435
                self._leave_lock = True
 
436
            else:
 
437
                self._leave_lock = False
 
438
            self._lock_mode = 'w'
 
439
            self._lock_count = 1
 
440
        elif self._lock_mode == 'r':
 
441
            raise errors.ReadOnlyError(self)
 
442
        else:
 
443
            self._lock_count += 1
 
444
        return self._lock_token
 
445
 
 
446
    def leave_lock_in_place(self):
 
447
        self._leave_lock = True
 
448
 
 
449
    def dont_leave_lock_in_place(self):
 
450
        self._leave_lock = False
 
451
 
 
452
    def _set_real_repository(self, repository):
 
453
        """Set the _real_repository for this repository.
 
454
 
 
455
        :param repository: The repository to fallback to for non-hpss
 
456
            implemented operations.
 
457
        """
 
458
        assert not isinstance(repository, RemoteRepository)
 
459
        self._real_repository = repository
 
460
        if self._lock_mode == 'w':
 
461
            # if we are already locked, the real repository must be able to
 
462
            # acquire the lock with our token.
 
463
            self._real_repository.lock_write(self._lock_token)
 
464
        elif self._lock_mode == 'r':
 
465
            self._real_repository.lock_read()
 
466
 
 
467
    def start_write_group(self):
 
468
        """Start a write group on the decorated repository.
 
469
        
 
470
        Smart methods peform operations in a single step so this api
 
471
        is not really applicable except as a compatibility thunk
 
472
        for older plugins that don't use e.g. the CommitBuilder
 
473
        facility.
 
474
        """
 
475
        self._ensure_real()
 
476
        return self._real_repository.start_write_group()
 
477
 
 
478
    def _unlock(self, token):
 
479
        path = self.bzrdir._path_for_remote_call(self._client)
 
480
        response = self._client.call('Repository.unlock', path, token)
 
481
        if response == ('ok',):
 
482
            return
 
483
        elif response[0] == 'TokenMismatch':
 
484
            raise errors.TokenMismatch(token, '(remote token)')
 
485
        else:
 
486
            raise errors.UnexpectedSmartServerResponse(response)
 
487
 
 
488
    def unlock(self):
 
489
        if self._lock_count == 1 and self._lock_mode == 'w':
 
490
            # don't unlock if inside a write group.
 
491
            if self.is_in_write_group():
 
492
                raise errors.BzrError(
 
493
                    'Must end write groups before releasing write locks.')
 
494
        self._lock_count -= 1
 
495
        if not self._lock_count:
 
496
            mode = self._lock_mode
 
497
            self._lock_mode = None
 
498
            if self._real_repository is not None:
 
499
                self._real_repository.unlock()
 
500
            if mode != 'w':
 
501
                # Only write-locked repositories need to make a remote method
 
502
                # call to perfom the unlock.
 
503
                return
 
504
            assert self._lock_token, 'Locked, but no token!'
 
505
            token = self._lock_token
 
506
            self._lock_token = None
 
507
            if not self._leave_lock:
 
508
                self._unlock(token)
 
509
 
 
510
    def break_lock(self):
 
511
        # should hand off to the network
 
512
        self._ensure_real()
 
513
        return self._real_repository.break_lock()
 
514
 
 
515
    def _get_tarball(self, compression):
 
516
        """Return a TemporaryFile containing a repository tarball.
 
517
        
 
518
        Returns None if the server does not support sending tarballs.
 
519
        """
 
520
        import tempfile
 
521
        path = self.bzrdir._path_for_remote_call(self._client)
 
522
        response, protocol = self._client.call_expecting_body(
 
523
            'Repository.tarball', path, compression)
 
524
        if response[0] == 'ok':
 
525
            # Extract the tarball and return it
 
526
            t = tempfile.NamedTemporaryFile()
 
527
            # TODO: rpc layer should read directly into it...
 
528
            t.write(protocol.read_body_bytes())
 
529
            t.seek(0)
 
530
            return t
 
531
        if (response == ('error', "Generic bzr smart protocol error: "
 
532
                "bad request 'Repository.tarball'") or
 
533
              response == ('error', "Generic bzr smart protocol error: "
 
534
                "bad request u'Repository.tarball'")):
 
535
            protocol.cancel_read_body()
 
536
            return None
 
537
        raise errors.UnexpectedSmartServerResponse(response)
 
538
 
 
539
    def sprout(self, to_bzrdir, revision_id=None):
 
540
        # TODO: Option to control what format is created?
 
541
        to_repo = self._copy_repository_tarball(to_bzrdir, revision_id)
 
542
        if to_repo is None:
 
543
            self._ensure_real()
 
544
            return self._real_repository.sprout(
 
545
                to_bzrdir, revision_id=revision_id)
 
546
        else:
 
547
            return to_repo
 
548
 
 
549
    ### These methods are just thin shims to the VFS object for now.
 
550
 
 
551
    def revision_tree(self, revision_id):
 
552
        self._ensure_real()
 
553
        return self._real_repository.revision_tree(revision_id)
 
554
 
 
555
    def get_serializer_format(self):
 
556
        self._ensure_real()
 
557
        return self._real_repository.get_serializer_format()
 
558
 
 
559
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
560
                           timezone=None, committer=None, revprops=None,
 
561
                           revision_id=None):
 
562
        # FIXME: It ought to be possible to call this without immediately
 
563
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
564
        self._ensure_real()
 
565
        builder = self._real_repository.get_commit_builder(branch, parents,
 
566
                config, timestamp=timestamp, timezone=timezone,
 
567
                committer=committer, revprops=revprops, revision_id=revision_id)
 
568
        # Make the builder use this RemoteRepository rather than the real one.
 
569
        builder.repository = self
 
570
        return builder
 
571
 
 
572
    @needs_write_lock
 
573
    def add_inventory(self, revid, inv, parents):
 
574
        self._ensure_real()
 
575
        return self._real_repository.add_inventory(revid, inv, parents)
 
576
 
 
577
    @needs_write_lock
 
578
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
579
        self._ensure_real()
 
580
        return self._real_repository.add_revision(
 
581
            rev_id, rev, inv=inv, config=config)
 
582
 
 
583
    @needs_read_lock
 
584
    def get_inventory(self, revision_id):
 
585
        self._ensure_real()
 
586
        return self._real_repository.get_inventory(revision_id)
 
587
 
 
588
    @needs_read_lock
 
589
    def get_revision(self, revision_id):
 
590
        self._ensure_real()
 
591
        return self._real_repository.get_revision(revision_id)
 
592
 
 
593
    @property
 
594
    def weave_store(self):
 
595
        self._ensure_real()
 
596
        return self._real_repository.weave_store
 
597
 
 
598
    def get_transaction(self):
 
599
        self._ensure_real()
 
600
        return self._real_repository.get_transaction()
 
601
 
 
602
    @needs_read_lock
 
603
    def clone(self, a_bzrdir, revision_id=None):
 
604
        self._ensure_real()
 
605
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
606
 
 
607
    def make_working_trees(self):
 
608
        """RemoteRepositories never create working trees by default."""
 
609
        return False
 
610
 
 
611
    def fetch(self, source, revision_id=None, pb=None):
 
612
        self._ensure_real()
 
613
        return self._real_repository.fetch(
 
614
            source, revision_id=revision_id, pb=pb)
 
615
 
 
616
    def create_bundle(self, target, base, fileobj, format=None):
 
617
        self._ensure_real()
 
618
        self._real_repository.create_bundle(target, base, fileobj, format)
 
619
 
 
620
    @property
 
621
    def control_weaves(self):
 
622
        self._ensure_real()
 
623
        return self._real_repository.control_weaves
 
624
 
 
625
    @needs_read_lock
 
626
    def get_ancestry(self, revision_id, topo_sorted=True):
 
627
        self._ensure_real()
 
628
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
629
 
 
630
    @needs_read_lock
 
631
    def get_inventory_weave(self):
 
632
        self._ensure_real()
 
633
        return self._real_repository.get_inventory_weave()
 
634
 
 
635
    def fileids_altered_by_revision_ids(self, revision_ids):
 
636
        self._ensure_real()
 
637
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
638
 
 
639
    def get_versioned_file_checker(self, revisions, revision_versions_cache):
 
640
        self._ensure_real()
 
641
        return self._real_repository.get_versioned_file_checker(
 
642
            revisions, revision_versions_cache)
 
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=None):
 
700
        self._ensure_real()
 
701
        return self._real_repository.check(revision_ids=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
    def revision_graph_can_have_wrong_parents(self):
 
783
        # The answer depends on the remote repo format.
 
784
        self._ensure_real()
 
785
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
786
 
 
787
    def _find_inconsistent_revision_parents(self):
 
788
        self._ensure_real()
 
789
        return self._real_repository._find_inconsistent_revision_parents()
 
790
 
 
791
    def _check_for_inconsistent_revision_parents(self):
 
792
        self._ensure_real()
 
793
        return self._real_repository._check_for_inconsistent_revision_parents()
 
794
 
 
795
 
 
796
class RemoteBranchLockableFiles(LockableFiles):
 
797
    """A 'LockableFiles' implementation that talks to a smart server.
 
798
    
 
799
    This is not a public interface class.
 
800
    """
 
801
 
 
802
    def __init__(self, bzrdir, _client):
 
803
        self.bzrdir = bzrdir
 
804
        self._client = _client
 
805
        self._need_find_modes = True
 
806
        LockableFiles.__init__(
 
807
            self, bzrdir.get_branch_transport(None),
 
808
            'lock', lockdir.LockDir)
 
809
 
 
810
    def _find_modes(self):
 
811
        # RemoteBranches don't let the client set the mode of control files.
 
812
        self._dir_mode = None
 
813
        self._file_mode = None
 
814
 
 
815
    def get(self, path):
 
816
        """'get' a remote path as per the LockableFiles interface.
 
817
 
 
818
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
819
             just retrieve a file, instead we ask the smart server to generate
 
820
             a configuration for us - which is retrieved as an INI file.
 
821
        """
 
822
        if path == 'branch.conf':
 
823
            path = self.bzrdir._path_for_remote_call(self._client)
 
824
            response = self._client.call_expecting_body(
 
825
                'Branch.get_config_file', path)
 
826
            assert response[0][0] == 'ok', \
 
827
                'unexpected response code %s' % (response[0],)
 
828
            return StringIO(response[1].read_body_bytes())
 
829
        else:
 
830
            # VFS fallback.
 
831
            return LockableFiles.get(self, path)
 
832
 
 
833
 
 
834
class RemoteBranchFormat(branch.BranchFormat):
 
835
 
 
836
    def __eq__(self, other):
 
837
        return (isinstance(other, RemoteBranchFormat) and 
 
838
            self.__dict__ == other.__dict__)
 
839
 
 
840
    def get_format_description(self):
 
841
        return 'Remote BZR Branch'
 
842
 
 
843
    def get_format_string(self):
 
844
        return 'Remote BZR Branch'
 
845
 
 
846
    def open(self, a_bzrdir):
 
847
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
848
        return a_bzrdir.open_branch()
 
849
 
 
850
    def initialize(self, a_bzrdir):
 
851
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
852
        return a_bzrdir.create_branch()
 
853
 
 
854
    def supports_tags(self):
 
855
        # Remote branches might support tags, but we won't know until we
 
856
        # access the real remote branch.
 
857
        return True
 
858
 
 
859
 
 
860
class RemoteBranch(branch.Branch):
 
861
    """Branch stored on a server accessed by HPSS RPC.
 
862
 
 
863
    At the moment most operations are mapped down to simple file operations.
 
864
    """
 
865
 
 
866
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
867
        _client=None):
 
868
        """Create a RemoteBranch instance.
 
869
 
 
870
        :param real_branch: An optional local implementation of the branch
 
871
            format, usually accessing the data via the VFS.
 
872
        :param _client: Private parameter for testing.
 
873
        """
 
874
        # We intentionally don't call the parent class's __init__, because it
 
875
        # will try to assign to self.tags, which is a property in this subclass.
 
876
        # And the parent's __init__ doesn't do much anyway.
 
877
        self._revision_history_cache = None
 
878
        self.bzrdir = remote_bzrdir
 
879
        if _client is not None:
 
880
            self._client = _client
 
881
        else:
 
882
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
883
        self.repository = remote_repository
 
884
        if real_branch is not None:
 
885
            self._real_branch = real_branch
 
886
            # Give the remote repository the matching real repo.
 
887
            real_repo = self._real_branch.repository
 
888
            if isinstance(real_repo, RemoteRepository):
 
889
                real_repo._ensure_real()
 
890
                real_repo = real_repo._real_repository
 
891
            self.repository._set_real_repository(real_repo)
 
892
            # Give the branch the remote repository to let fast-pathing happen.
 
893
            self._real_branch.repository = self.repository
 
894
        else:
 
895
            self._real_branch = None
 
896
        # Fill out expected attributes of branch for bzrlib api users.
 
897
        self._format = RemoteBranchFormat()
 
898
        self.base = self.bzrdir.root_transport.base
 
899
        self._control_files = None
 
900
        self._lock_mode = None
 
901
        self._lock_token = None
 
902
        self._lock_count = 0
 
903
        self._leave_lock = False
 
904
 
 
905
    def __str__(self):
 
906
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
907
 
 
908
    __repr__ = __str__
 
909
 
 
910
    def _ensure_real(self):
 
911
        """Ensure that there is a _real_branch set.
 
912
 
 
913
        Used before calls to self._real_branch.
 
914
        """
 
915
        if not self._real_branch:
 
916
            assert vfs.vfs_enabled()
 
917
            self.bzrdir._ensure_real()
 
918
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
919
            # Give the remote repository the matching real repo.
 
920
            real_repo = self._real_branch.repository
 
921
            if isinstance(real_repo, RemoteRepository):
 
922
                real_repo._ensure_real()
 
923
                real_repo = real_repo._real_repository
 
924
            self.repository._set_real_repository(real_repo)
 
925
            # Give the branch the remote repository to let fast-pathing happen.
 
926
            self._real_branch.repository = self.repository
 
927
            # XXX: deal with _lock_mode == 'w'
 
928
            if self._lock_mode == 'r':
 
929
                self._real_branch.lock_read()
 
930
 
 
931
    @property
 
932
    def control_files(self):
 
933
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
934
        # because it triggers an _ensure_real that we otherwise might not need.
 
935
        if self._control_files is None:
 
936
            self._control_files = RemoteBranchLockableFiles(
 
937
                self.bzrdir, self._client)
 
938
        return self._control_files
 
939
 
 
940
    def _get_checkout_format(self):
 
941
        self._ensure_real()
 
942
        return self._real_branch._get_checkout_format()
 
943
 
 
944
    def get_physical_lock_status(self):
 
945
        """See Branch.get_physical_lock_status()."""
 
946
        # should be an API call to the server, as branches must be lockable.
 
947
        self._ensure_real()
 
948
        return self._real_branch.get_physical_lock_status()
 
949
 
 
950
    def lock_read(self):
 
951
        if not self._lock_mode:
 
952
            self._lock_mode = 'r'
 
953
            self._lock_count = 1
 
954
            if self._real_branch is not None:
 
955
                self._real_branch.lock_read()
 
956
        else:
 
957
            self._lock_count += 1
 
958
 
 
959
    def _remote_lock_write(self, token):
 
960
        if token is None:
 
961
            branch_token = repo_token = ''
 
962
        else:
 
963
            branch_token = token
 
964
            repo_token = self.repository.lock_write()
 
965
            self.repository.unlock()
 
966
        path = self.bzrdir._path_for_remote_call(self._client)
 
967
        response = self._client.call('Branch.lock_write', path, branch_token,
 
968
                                     repo_token)
 
969
        if response[0] == 'ok':
 
970
            ok, branch_token, repo_token = response
 
971
            return branch_token, repo_token
 
972
        elif response[0] == 'LockContention':
 
973
            raise errors.LockContention('(remote lock)')
 
974
        elif response[0] == 'TokenMismatch':
 
975
            raise errors.TokenMismatch(token, '(remote token)')
 
976
        elif response[0] == 'UnlockableTransport':
 
977
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
978
        elif response[0] == 'ReadOnlyError':
 
979
            raise errors.ReadOnlyError(self)
 
980
        else:
 
981
            raise errors.UnexpectedSmartServerResponse(response)
 
982
            
 
983
    def lock_write(self, token=None):
 
984
        if not self._lock_mode:
 
985
            remote_tokens = self._remote_lock_write(token)
 
986
            self._lock_token, self._repo_lock_token = remote_tokens
 
987
            assert self._lock_token, 'Remote server did not return a token!'
 
988
            # TODO: We really, really, really don't want to call _ensure_real
 
989
            # here, but it's the easiest way to ensure coherency between the
 
990
            # state of the RemoteBranch and RemoteRepository objects and the
 
991
            # physical locks.  If we don't materialise the real objects here,
 
992
            # then getting everything in the right state later is complex, so
 
993
            # for now we just do it the lazy way.
 
994
            #   -- Andrew Bennetts, 2007-02-22.
 
995
            self._ensure_real()
 
996
            if self._real_branch is not None:
 
997
                self._real_branch.repository.lock_write(
 
998
                    token=self._repo_lock_token)
 
999
                try:
 
1000
                    self._real_branch.lock_write(token=self._lock_token)
 
1001
                finally:
 
1002
                    self._real_branch.repository.unlock()
 
1003
            if token is not None:
 
1004
                self._leave_lock = True
 
1005
            else:
 
1006
                # XXX: this case seems to be unreachable; token cannot be None.
 
1007
                self._leave_lock = False
 
1008
            self._lock_mode = 'w'
 
1009
            self._lock_count = 1
 
1010
        elif self._lock_mode == 'r':
 
1011
            raise errors.ReadOnlyTransaction
 
1012
        else:
 
1013
            if token is not None:
 
1014
                # A token was given to lock_write, and we're relocking, so check
 
1015
                # that the given token actually matches the one we already have.
 
1016
                if token != self._lock_token:
 
1017
                    raise errors.TokenMismatch(token, self._lock_token)
 
1018
            self._lock_count += 1
 
1019
        return self._lock_token
 
1020
 
 
1021
    def _unlock(self, branch_token, repo_token):
 
1022
        path = self.bzrdir._path_for_remote_call(self._client)
 
1023
        response = self._client.call('Branch.unlock', path, branch_token,
 
1024
                                     repo_token)
 
1025
        if response == ('ok',):
 
1026
            return
 
1027
        elif response[0] == 'TokenMismatch':
 
1028
            raise errors.TokenMismatch(
 
1029
                str((branch_token, repo_token)), '(remote tokens)')
 
1030
        else:
 
1031
            raise errors.UnexpectedSmartServerResponse(response)
 
1032
 
 
1033
    def unlock(self):
 
1034
        self._lock_count -= 1
 
1035
        if not self._lock_count:
 
1036
            self._clear_cached_state()
 
1037
            mode = self._lock_mode
 
1038
            self._lock_mode = None
 
1039
            if self._real_branch is not None:
 
1040
                if not self._leave_lock:
 
1041
                    # If this RemoteBranch will remove the physical lock for the
 
1042
                    # repository, make sure the _real_branch doesn't do it
 
1043
                    # first.  (Because the _real_branch's repository is set to
 
1044
                    # be the RemoteRepository.)
 
1045
                    self._real_branch.repository.leave_lock_in_place()
 
1046
                self._real_branch.unlock()
 
1047
            if mode != 'w':
 
1048
                # Only write-locked branched need to make a remote method call
 
1049
                # to perfom the unlock.
 
1050
                return
 
1051
            assert self._lock_token, 'Locked, but no token!'
 
1052
            branch_token = self._lock_token
 
1053
            repo_token = self._repo_lock_token
 
1054
            self._lock_token = None
 
1055
            self._repo_lock_token = None
 
1056
            if not self._leave_lock:
 
1057
                self._unlock(branch_token, repo_token)
 
1058
 
 
1059
    def break_lock(self):
 
1060
        self._ensure_real()
 
1061
        return self._real_branch.break_lock()
 
1062
 
 
1063
    def leave_lock_in_place(self):
 
1064
        self._leave_lock = True
 
1065
 
 
1066
    def dont_leave_lock_in_place(self):
 
1067
        self._leave_lock = False
 
1068
 
 
1069
    def last_revision_info(self):
 
1070
        """See Branch.last_revision_info()."""
 
1071
        path = self.bzrdir._path_for_remote_call(self._client)
 
1072
        response = self._client.call('Branch.last_revision_info', path)
 
1073
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
1074
        revno = int(response[1])
 
1075
        last_revision = response[2]
 
1076
        return (revno, last_revision)
 
1077
 
 
1078
    def _gen_revision_history(self):
 
1079
        """See Branch._gen_revision_history()."""
 
1080
        path = self.bzrdir._path_for_remote_call(self._client)
 
1081
        response = self._client.call_expecting_body(
 
1082
            'Branch.revision_history', path)
 
1083
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1084
                                        % (response[0],))
 
1085
        result = response[1].read_body_bytes().split('\x00')
 
1086
        if result == ['']:
 
1087
            return []
 
1088
        return result
 
1089
 
 
1090
    @needs_write_lock
 
1091
    def set_revision_history(self, rev_history):
 
1092
        # Send just the tip revision of the history; the server will generate
 
1093
        # the full history from that.  If the revision doesn't exist in this
 
1094
        # branch, NoSuchRevision will be raised.
 
1095
        path = self.bzrdir._path_for_remote_call(self._client)
 
1096
        if rev_history == []:
 
1097
            rev_id = 'null:'
 
1098
        else:
 
1099
            rev_id = rev_history[-1]
 
1100
        self._clear_cached_state()
 
1101
        response = self._client.call('Branch.set_last_revision',
 
1102
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1103
        if response[0] == 'NoSuchRevision':
 
1104
            raise NoSuchRevision(self, rev_id)
 
1105
        else:
 
1106
            assert response == ('ok',), (
 
1107
                'unexpected response code %r' % (response,))
 
1108
        self._cache_revision_history(rev_history)
 
1109
 
 
1110
    def get_parent(self):
 
1111
        self._ensure_real()
 
1112
        return self._real_branch.get_parent()
 
1113
        
 
1114
    def set_parent(self, url):
 
1115
        self._ensure_real()
 
1116
        return self._real_branch.set_parent(url)
 
1117
        
 
1118
    def get_config(self):
 
1119
        return RemoteBranchConfig(self)
 
1120
 
 
1121
    def sprout(self, to_bzrdir, revision_id=None):
 
1122
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1123
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1124
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1125
        # to_bzrdir.create_branch...
 
1126
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
1127
        self.copy_content_into(result, revision_id=revision_id)
 
1128
        result.set_parent(self.bzrdir.root_transport.base)
 
1129
        return result
 
1130
 
 
1131
    @needs_write_lock
 
1132
    def pull(self, source, overwrite=False, stop_revision=None,
 
1133
             **kwargs):
 
1134
        # FIXME: This asks the real branch to run the hooks, which means
 
1135
        # they're called with the wrong target branch parameter. 
 
1136
        # The test suite specifically allows this at present but it should be
 
1137
        # fixed.  It should get a _override_hook_target branch,
 
1138
        # as push does.  -- mbp 20070405
 
1139
        self._ensure_real()
 
1140
        self._real_branch.pull(
 
1141
            source, overwrite=overwrite, stop_revision=stop_revision,
 
1142
            **kwargs)
 
1143
 
 
1144
    @needs_read_lock
 
1145
    def push(self, target, overwrite=False, stop_revision=None):
 
1146
        self._ensure_real()
 
1147
        return self._real_branch.push(
 
1148
            target, overwrite=overwrite, stop_revision=stop_revision,
 
1149
            _override_hook_source_branch=self)
 
1150
 
 
1151
    def is_locked(self):
 
1152
        return self._lock_count >= 1
 
1153
 
 
1154
    def set_last_revision_info(self, revno, revision_id):
 
1155
        self._ensure_real()
 
1156
        self._clear_cached_state()
 
1157
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
1158
 
 
1159
    def generate_revision_history(self, revision_id, last_rev=None,
 
1160
                                  other_branch=None):
 
1161
        self._ensure_real()
 
1162
        return self._real_branch.generate_revision_history(
 
1163
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
1164
 
 
1165
    @property
 
1166
    def tags(self):
 
1167
        self._ensure_real()
 
1168
        return self._real_branch.tags
 
1169
 
 
1170
    def set_push_location(self, location):
 
1171
        self._ensure_real()
 
1172
        return self._real_branch.set_push_location(location)
 
1173
 
 
1174
    def update_revisions(self, other, stop_revision=None):
 
1175
        self._ensure_real()
 
1176
        return self._real_branch.update_revisions(
 
1177
            other, stop_revision=stop_revision)
 
1178
 
 
1179
 
 
1180
class RemoteBranchConfig(BranchConfig):
 
1181
 
 
1182
    def username(self):
 
1183
        self.branch._ensure_real()
 
1184
        return self.branch._real_branch.get_config().username()
 
1185
 
 
1186
    def _get_branch_data_config(self):
 
1187
        self.branch._ensure_real()
 
1188
        if self._branch_data_config is None:
 
1189
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1190
        return self._branch_data_config
 
1191
 
 
1192
 
 
1193
def _extract_tar(tar, to_dir):
 
1194
    """Extract all the contents of a tarfile object.
 
1195
 
 
1196
    A replacement for extractall, which is not present in python2.4
 
1197
    """
 
1198
    for tarinfo in tar:
 
1199
        tar.extract(tarinfo, to_dir)