~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-09-20 02:40:52 UTC
  • mfrom: (2835.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070920024052-y2l7r5o00zrpnr73
No longer propagate index differences automatically (Robert Collins)

Show diffs side-by-side

added added

removed removed

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