~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Pool
  • Date: 2005-09-13 23:08:19 UTC
  • Revision ID: mbp@sourcefrog.net-20050913230819-6ceae96050d32faa
ignore .bzr-shelf

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