~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Aaron Bentley
  • Date: 2007-09-13 01:54:20 UTC
  • mto: This revision was merged to the branch mainline in revision 2826.
  • Revision ID: aaron.bentley@utoronto.ca-20070913015420-l766kszjaexug01y
Cleanups

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