/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-08-30 14:56:51 UTC
  • mto: This revision was merged to the branch mainline in revision 3174.
  • Revision ID: andrew.bennetts@canonical.com-20070830145651-6r0rwb6ga20803q5
Nasty hackery to make stream_knit_data_for_revisions response use streaming.

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