/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: Canonical.com Patch Queue Manager
  • Date: 2007-08-21 02:46:21 UTC
  • mfrom: (2681.1.40 send-bundle)
  • Revision ID: pqm@pqm.ubuntu.com-20070821024621-czmqk59igiyvsgk8
"send" now sends the message as an attachment

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