~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: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
# TODO: At some point, handle upgrades by just passing the whole request
18
 
# across to run on the server.
19
 
 
20
17
import bz2
21
18
 
22
19
from bzrlib import (
 
20
    bencode,
23
21
    branch,
24
22
    bzrdir,
 
23
    config,
 
24
    controldir,
25
25
    debug,
26
26
    errors,
27
27
    graph,
 
28
    lock,
28
29
    lockdir,
29
 
    pack,
30
30
    repository,
 
31
    repository as _mod_repository,
31
32
    revision,
 
33
    revision as _mod_revision,
 
34
    static_tuple,
32
35
    symbol_versioning,
33
 
    urlutils,
34
36
)
35
 
from bzrlib.branch import BranchReferenceFormat
 
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
36
38
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
37
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
39
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
38
40
from bzrlib.errors import (
39
41
    NoSuchRevision,
40
42
    SmartProtocolError,
42
44
from bzrlib.lockable_files import LockableFiles
43
45
from bzrlib.smart import client, vfs, repository as smart_repo
44
46
from bzrlib.revision import ensure_null, NULL_REVISION
 
47
from bzrlib.repository import RepositoryWriteLockResult
45
48
from bzrlib.trace import mutter, note, warning
46
 
from bzrlib.util import bencode
47
49
 
48
50
 
49
51
class _RpcHelper(object):
61
63
        except errors.ErrorFromSmartServer, err:
62
64
            self._translate_error(err, **err_context)
63
65
 
 
66
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
 
67
        try:
 
68
            return self._client.call_with_body_bytes(method, args, body_bytes)
 
69
        except errors.ErrorFromSmartServer, err:
 
70
            self._translate_error(err, **err_context)
 
71
 
64
72
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
65
73
                                             **err_context):
66
74
        try:
85
93
class RemoteBzrDir(BzrDir, _RpcHelper):
86
94
    """Control directory on a remote server, accessed via bzr:// or similar."""
87
95
 
88
 
    def __init__(self, transport, format, _client=None):
 
96
    def __init__(self, transport, format, _client=None, _force_probe=False):
89
97
        """Construct a RemoteBzrDir.
90
98
 
91
99
        :param _client: Private parameter for testing. Disables probing and the
95
103
        # this object holds a delegated bzrdir that uses file-level operations
96
104
        # to talk to the other side
97
105
        self._real_bzrdir = None
 
106
        self._has_working_tree = None
98
107
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
108
        # create_branch for details.
100
109
        self._next_open_branch_result = None
104
113
            self._client = client._SmartClient(medium)
105
114
        else:
106
115
            self._client = _client
107
 
            return
108
 
 
 
116
            if not _force_probe:
 
117
                return
 
118
 
 
119
        self._probe_bzrdir()
 
120
 
 
121
    def __repr__(self):
 
122
        return '%s(%r)' % (self.__class__.__name__, self._client)
 
123
 
 
124
    def _probe_bzrdir(self):
 
125
        medium = self._client._medium
109
126
        path = self._path_for_remote_call(self._client)
 
127
        if medium._is_remote_before((2, 1)):
 
128
            self._rpc_open(path)
 
129
            return
 
130
        try:
 
131
            self._rpc_open_2_1(path)
 
132
            return
 
133
        except errors.UnknownSmartMethod:
 
134
            medium._remember_remote_is_before((2, 1))
 
135
            self._rpc_open(path)
 
136
 
 
137
    def _rpc_open_2_1(self, path):
 
138
        response = self._call('BzrDir.open_2.1', path)
 
139
        if response == ('no',):
 
140
            raise errors.NotBranchError(path=self.root_transport.base)
 
141
        elif response[0] == 'yes':
 
142
            if response[1] == 'yes':
 
143
                self._has_working_tree = True
 
144
            elif response[1] == 'no':
 
145
                self._has_working_tree = False
 
146
            else:
 
147
                raise errors.UnexpectedSmartServerResponse(response)
 
148
        else:
 
149
            raise errors.UnexpectedSmartServerResponse(response)
 
150
 
 
151
    def _rpc_open(self, path):
110
152
        response = self._call('BzrDir.open', path)
111
153
        if response not in [('yes',), ('no',)]:
112
154
            raise errors.UnexpectedSmartServerResponse(response)
113
155
        if response == ('no',):
114
 
            raise errors.NotBranchError(path=transport.base)
 
156
            raise errors.NotBranchError(path=self.root_transport.base)
115
157
 
116
158
    def _ensure_real(self):
117
159
        """Ensure that there is a _real_bzrdir set.
119
161
        Used before calls to self._real_bzrdir.
120
162
        """
121
163
        if not self._real_bzrdir:
 
164
            if 'hpssvfs' in debug.debug_flags:
 
165
                import traceback
 
166
                warning('VFS BzrDir access triggered\n%s',
 
167
                    ''.join(traceback.format_stack()))
122
168
            self._real_bzrdir = BzrDir.open_from_transport(
123
169
                self.root_transport, _server_formats=False)
124
170
            self._format._network_name = \
200
246
        self._ensure_real()
201
247
        self._real_bzrdir.destroy_repository()
202
248
 
203
 
    def create_branch(self):
 
249
    def create_branch(self, name=None):
204
250
        # as per meta1 formats - just delegate to the format object which may
205
251
        # be parameterised.
206
 
        real_branch = self._format.get_branch_format().initialize(self)
 
252
        real_branch = self._format.get_branch_format().initialize(self,
 
253
            name=name)
207
254
        if not isinstance(real_branch, RemoteBranch):
208
 
            result = RemoteBranch(self, self.find_repository(), real_branch)
 
255
            result = RemoteBranch(self, self.find_repository(), real_branch,
 
256
                                  name=name)
209
257
        else:
210
258
            result = real_branch
211
259
        # BzrDir.clone_on_transport() uses the result of create_branch but does
217
265
        self._next_open_branch_result = result
218
266
        return result
219
267
 
220
 
    def destroy_branch(self):
 
268
    def destroy_branch(self, name=None):
221
269
        """See BzrDir.destroy_branch"""
222
270
        self._ensure_real()
223
 
        self._real_bzrdir.destroy_branch()
 
271
        self._real_bzrdir.destroy_branch(name=name)
224
272
        self._next_open_branch_result = None
225
273
 
226
274
    def create_workingtree(self, revision_id=None, from_branch=None):
227
275
        raise errors.NotLocalUrl(self.transport.base)
228
276
 
229
 
    def find_branch_format(self):
 
277
    def find_branch_format(self, name=None):
230
278
        """Find the branch 'format' for this bzrdir.
231
279
 
232
280
        This might be a synthetic object for e.g. RemoteBranch and SVN.
233
281
        """
234
 
        b = self.open_branch()
 
282
        b = self.open_branch(name=name)
235
283
        return b._format
236
284
 
237
 
    def get_branch_reference(self):
 
285
    def get_branch_reference(self, name=None):
238
286
        """See BzrDir.get_branch_reference()."""
 
287
        if name is not None:
 
288
            # XXX JRV20100304: Support opening colocated branches
 
289
            raise errors.NoColocatedBranchSupport(self)
239
290
        response = self._get_branch_reference()
240
291
        if response[0] == 'ref':
241
292
            return response[1]
245
296
    def _get_branch_reference(self):
246
297
        path = self._path_for_remote_call(self._client)
247
298
        medium = self._client._medium
248
 
        if not medium._is_remote_before((1, 13)):
 
299
        candidate_calls = [
 
300
            ('BzrDir.open_branchV3', (2, 1)),
 
301
            ('BzrDir.open_branchV2', (1, 13)),
 
302
            ('BzrDir.open_branch', None),
 
303
            ]
 
304
        for verb, required_version in candidate_calls:
 
305
            if required_version and medium._is_remote_before(required_version):
 
306
                continue
249
307
            try:
250
 
                response = self._call('BzrDir.open_branchV2', path)
251
 
                if response[0] not in ('ref', 'branch'):
252
 
                    raise errors.UnexpectedSmartServerResponse(response)
253
 
                return response
 
308
                response = self._call(verb, path)
254
309
            except errors.UnknownSmartMethod:
255
 
                medium._remember_remote_is_before((1, 13))
256
 
        response = self._call('BzrDir.open_branch', path)
257
 
        if response[0] != 'ok':
 
310
                if required_version is None:
 
311
                    raise
 
312
                medium._remember_remote_is_before(required_version)
 
313
            else:
 
314
                break
 
315
        if verb == 'BzrDir.open_branch':
 
316
            if response[0] != 'ok':
 
317
                raise errors.UnexpectedSmartServerResponse(response)
 
318
            if response[1] != '':
 
319
                return ('ref', response[1])
 
320
            else:
 
321
                return ('branch', '')
 
322
        if response[0] not in ('ref', 'branch'):
258
323
            raise errors.UnexpectedSmartServerResponse(response)
259
 
        if response[1] != '':
260
 
            return ('ref', response[1])
261
 
        else:
262
 
            return ('branch', '')
 
324
        return response
263
325
 
264
 
    def _get_tree_branch(self):
 
326
    def _get_tree_branch(self, name=None):
265
327
        """See BzrDir._get_tree_branch()."""
266
 
        return None, self.open_branch()
 
328
        return None, self.open_branch(name=name)
267
329
 
268
 
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
269
 
        if _unsupported:
 
330
    def open_branch(self, name=None, unsupported=False,
 
331
                    ignore_fallbacks=False):
 
332
        if unsupported:
270
333
            raise NotImplementedError('unsupported flag support not implemented yet.')
271
334
        if self._next_open_branch_result is not None:
272
335
            # See create_branch for details.
277
340
        if response[0] == 'ref':
278
341
            # a branch reference, use the existing BranchReference logic.
279
342
            format = BranchReferenceFormat()
280
 
            return format.open(self, _found=True, location=response[1],
281
 
                ignore_fallbacks=ignore_fallbacks)
 
343
            return format.open(self, name=name, _found=True,
 
344
                location=response[1], ignore_fallbacks=ignore_fallbacks)
282
345
        branch_format_name = response[1]
283
346
        if not branch_format_name:
284
347
            branch_format_name = None
285
348
        format = RemoteBranchFormat(network_name=branch_format_name)
286
349
        return RemoteBranch(self, self.find_repository(), format=format,
287
 
            setup_stacking=not ignore_fallbacks)
 
350
            setup_stacking=not ignore_fallbacks, name=name)
288
351
 
289
352
    def _open_repo_v1(self, path):
290
353
        verb = 'BzrDir.find_repository'
351
414
        else:
352
415
            raise errors.NoRepositoryPresent(self)
353
416
 
 
417
    def has_workingtree(self):
 
418
        if self._has_working_tree is None:
 
419
            self._ensure_real()
 
420
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
421
        return self._has_working_tree
 
422
 
354
423
    def open_workingtree(self, recommend_upgrade=True):
355
 
        self._ensure_real()
356
 
        if self._real_bzrdir.has_workingtree():
 
424
        if self.has_workingtree():
357
425
            raise errors.NotLocalUrl(self.root_transport)
358
426
        else:
359
427
            raise errors.NoWorkingTree(self.root_transport.base)
362
430
        """Return the path to be used for this bzrdir in a remote call."""
363
431
        return client.remote_path_from_transport(self.root_transport)
364
432
 
365
 
    def get_branch_transport(self, branch_format):
 
433
    def get_branch_transport(self, branch_format, name=None):
366
434
        self._ensure_real()
367
 
        return self._real_bzrdir.get_branch_transport(branch_format)
 
435
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
368
436
 
369
437
    def get_repository_transport(self, repository_format):
370
438
        self._ensure_real()
391
459
        return self._real_bzrdir.clone(url, revision_id=revision_id,
392
460
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
393
461
 
394
 
    def get_config(self):
395
 
        self._ensure_real()
396
 
        return self._real_bzrdir.get_config()
 
462
    def _get_config(self):
 
463
        return RemoteBzrDirConfig(self)
397
464
 
398
465
 
399
466
class RemoteRepositoryFormat(repository.RepositoryFormat):
423
490
        self._custom_format = None
424
491
        self._network_name = None
425
492
        self._creating_bzrdir = None
 
493
        self._supports_chks = None
426
494
        self._supports_external_lookups = None
427
495
        self._supports_tree_reference = None
428
496
        self._rich_root_data = None
429
497
 
 
498
    def __repr__(self):
 
499
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
500
            self._network_name)
 
501
 
430
502
    @property
431
503
    def fast_deltas(self):
432
504
        self._ensure_real()
440
512
        return self._rich_root_data
441
513
 
442
514
    @property
 
515
    def supports_chks(self):
 
516
        if self._supports_chks is None:
 
517
            self._ensure_real()
 
518
            self._supports_chks = self._custom_format.supports_chks
 
519
        return self._supports_chks
 
520
 
 
521
    @property
443
522
    def supports_external_lookups(self):
444
523
        if self._supports_external_lookups is None:
445
524
            self._ensure_real()
491
570
        # 1) get the network name to use.
492
571
        if self._custom_format:
493
572
            network_name = self._custom_format.network_name()
 
573
        elif self._network_name:
 
574
            network_name = self._network_name
494
575
        else:
495
576
            # Select the current bzrlib default and ask for that.
496
577
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
544
625
        return self._custom_format._fetch_reconcile
545
626
 
546
627
    def get_format_description(self):
547
 
        return 'bzr remote repository'
 
628
        self._ensure_real()
 
629
        return 'Remote: ' + self._custom_format.get_format_description()
548
630
 
549
631
    def __eq__(self, other):
550
632
        return self.__class__ is other.__class__
551
633
 
552
 
    def check_conversion_target(self, target_format):
553
 
        if self.rich_root_data and not target_format.rich_root_data:
554
 
            raise errors.BadConversionTarget(
555
 
                'Does not support rich root data.', target_format)
556
 
        if (self.supports_tree_reference and
557
 
            not getattr(target_format, 'supports_tree_reference', False)):
558
 
            raise errors.BadConversionTarget(
559
 
                'Does not support nested trees', target_format)
560
 
 
561
634
    def network_name(self):
562
635
        if self._network_name:
563
636
            return self._network_name
565
638
        return self._creating_repo._real_repository._format.network_name()
566
639
 
567
640
    @property
 
641
    def pack_compresses(self):
 
642
        self._ensure_real()
 
643
        return self._custom_format.pack_compresses
 
644
 
 
645
    @property
568
646
    def _serializer(self):
569
647
        self._ensure_real()
570
648
        return self._custom_format._serializer
571
649
 
572
650
 
573
 
class RemoteRepository(_RpcHelper):
 
651
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
 
652
    controldir.ControlComponent):
574
653
    """Repository accessed over rpc.
575
654
 
576
655
    For the moment most operations are performed using local transport-backed
602
681
        self._lock_token = None
603
682
        self._lock_count = 0
604
683
        self._leave_lock = False
 
684
        # Cache of revision parents; misses are cached during read locks, and
 
685
        # write locks when no _real_repository has been set.
605
686
        self._unstacked_provider = graph.CachingParentsProvider(
606
687
            get_parent_map=self._get_parent_map_rpc)
607
688
        self._unstacked_provider.disable_cache()
617
698
        # Additional places to query for data.
618
699
        self._fallback_repositories = []
619
700
 
 
701
    @property
 
702
    def user_transport(self):
 
703
        return self.bzrdir.user_transport
 
704
 
 
705
    @property
 
706
    def control_transport(self):
 
707
        # XXX: Normally you shouldn't directly get at the remote repository
 
708
        # transport, but I'm not sure it's worth making this method
 
709
        # optional -- mbp 2010-04-21
 
710
        return self.bzrdir.get_repository_transport(None)
 
711
        
620
712
    def __str__(self):
621
713
        return "%s(%s)" % (self.__class__.__name__, self.base)
622
714
 
625
717
    def abort_write_group(self, suppress_errors=False):
626
718
        """Complete a write group on the decorated repository.
627
719
 
628
 
        Smart methods peform operations in a single step so this api
 
720
        Smart methods perform operations in a single step so this API
629
721
        is not really applicable except as a compatibility thunk
630
722
        for older plugins that don't use e.g. the CommitBuilder
631
723
        facility.
649
741
    def commit_write_group(self):
650
742
        """Complete a write group on the decorated repository.
651
743
 
652
 
        Smart methods peform operations in a single step so this api
 
744
        Smart methods perform operations in a single step so this API
653
745
        is not really applicable except as a compatibility thunk
654
746
        for older plugins that don't use e.g. the CommitBuilder
655
747
        facility.
665
757
        self._ensure_real()
666
758
        return self._real_repository.suspend_write_group()
667
759
 
 
760
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
761
        self._ensure_real()
 
762
        return self._real_repository.get_missing_parent_inventories(
 
763
            check_for_missing_texts=check_for_missing_texts)
 
764
 
 
765
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
 
766
        self._ensure_real()
 
767
        return self._real_repository.get_rev_id_for_revno(
 
768
            revno, known_pair)
 
769
 
 
770
    def get_rev_id_for_revno(self, revno, known_pair):
 
771
        """See Repository.get_rev_id_for_revno."""
 
772
        path = self.bzrdir._path_for_remote_call(self._client)
 
773
        try:
 
774
            if self._client._medium._is_remote_before((1, 17)):
 
775
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
776
            response = self._call(
 
777
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
778
        except errors.UnknownSmartMethod:
 
779
            self._client._medium._remember_remote_is_before((1, 17))
 
780
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
781
        if response[0] == 'ok':
 
782
            return True, response[1]
 
783
        elif response[0] == 'history-incomplete':
 
784
            known_pair = response[1:3]
 
785
            for fallback in self._fallback_repositories:
 
786
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
 
787
                if found:
 
788
                    return True, result
 
789
                else:
 
790
                    known_pair = result
 
791
            # Not found in any fallbacks
 
792
            return False, known_pair
 
793
        else:
 
794
            raise errors.UnexpectedSmartServerResponse(response)
 
795
 
668
796
    def _ensure_real(self):
669
797
        """Ensure that there is a _real_repository set.
670
798
 
679
807
        invocation. If in doubt chat to the bzr network team.
680
808
        """
681
809
        if self._real_repository is None:
 
810
            if 'hpssvfs' in debug.debug_flags:
 
811
                import traceback
 
812
                warning('VFS Repository access triggered\n%s',
 
813
                    ''.join(traceback.format_stack()))
 
814
            self._unstacked_provider.missing_keys.clear()
682
815
            self.bzrdir._ensure_real()
683
816
            self._set_real_repository(
684
817
                self.bzrdir._real_bzrdir.open_repository())
744
877
        """Return a source for streaming from this repository."""
745
878
        return RemoteStreamSource(self, to_format)
746
879
 
 
880
    @needs_read_lock
747
881
    def has_revision(self, revision_id):
748
 
        """See Repository.has_revision()."""
749
 
        if revision_id == NULL_REVISION:
750
 
            # The null revision is always present.
751
 
            return True
752
 
        path = self.bzrdir._path_for_remote_call(self._client)
753
 
        response = self._call('Repository.has_revision', path, revision_id)
754
 
        if response[0] not in ('yes', 'no'):
755
 
            raise errors.UnexpectedSmartServerResponse(response)
756
 
        if response[0] == 'yes':
757
 
            return True
758
 
        for fallback_repo in self._fallback_repositories:
759
 
            if fallback_repo.has_revision(revision_id):
760
 
                return True
761
 
        return False
 
882
        """True if this repository has a copy of the revision."""
 
883
        # Copy of bzrlib.repository.Repository.has_revision
 
884
        return revision_id in self.has_revisions((revision_id,))
762
885
 
 
886
    @needs_read_lock
763
887
    def has_revisions(self, revision_ids):
764
 
        """See Repository.has_revisions()."""
765
 
        # FIXME: This does many roundtrips, particularly when there are
766
 
        # fallback repositories.  -- mbp 20080905
767
 
        result = set()
768
 
        for revision_id in revision_ids:
769
 
            if self.has_revision(revision_id):
770
 
                result.add(revision_id)
 
888
        """Probe to find out the presence of multiple revisions.
 
889
 
 
890
        :param revision_ids: An iterable of revision_ids.
 
891
        :return: A set of the revision_ids that were present.
 
892
        """
 
893
        # Copy of bzrlib.repository.Repository.has_revisions
 
894
        parent_map = self.get_parent_map(revision_ids)
 
895
        result = set(parent_map)
 
896
        if _mod_revision.NULL_REVISION in revision_ids:
 
897
            result.add(_mod_revision.NULL_REVISION)
771
898
        return result
772
899
 
 
900
    def _has_same_fallbacks(self, other_repo):
 
901
        """Returns true if the repositories have the same fallbacks."""
 
902
        # XXX: copied from Repository; it should be unified into a base class
 
903
        # <https://bugs.launchpad.net/bzr/+bug/401622>
 
904
        my_fb = self._fallback_repositories
 
905
        other_fb = other_repo._fallback_repositories
 
906
        if len(my_fb) != len(other_fb):
 
907
            return False
 
908
        for f, g in zip(my_fb, other_fb):
 
909
            if not f.has_same_location(g):
 
910
                return False
 
911
        return True
 
912
 
773
913
    def has_same_location(self, other):
 
914
        # TODO: Move to RepositoryBase and unify with the regular Repository
 
915
        # one; unfortunately the tests rely on slightly different behaviour at
 
916
        # present -- mbp 20090710
774
917
        return (self.__class__ is other.__class__ and
775
918
                self.bzrdir.transport.base == other.bzrdir.transport.base)
776
919
 
779
922
        parents_provider = self._make_parents_provider(other_repository)
780
923
        return graph.Graph(parents_provider)
781
924
 
 
925
    @needs_read_lock
 
926
    def get_known_graph_ancestry(self, revision_ids):
 
927
        """Return the known graph for a set of revision ids and their ancestors.
 
928
        """
 
929
        st = static_tuple.StaticTuple
 
930
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
931
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
932
        return graph.GraphThunkIdsToKeys(known_graph)
 
933
 
782
934
    def gather_stats(self, revid=None, committers=None):
783
935
        """See Repository.gather_stats()."""
784
936
        path = self.bzrdir._path_for_remote_call(self._client)
844
996
    def is_write_locked(self):
845
997
        return self._lock_mode == 'w'
846
998
 
 
999
    def _warn_if_deprecated(self, branch=None):
 
1000
        # If we have a real repository, the check will be done there, if we
 
1001
        # don't the check will be done remotely.
 
1002
        pass
 
1003
 
847
1004
    def lock_read(self):
 
1005
        """Lock the repository for read operations.
 
1006
 
 
1007
        :return: A bzrlib.lock.LogicalLockResult.
 
1008
        """
848
1009
        # wrong eventually - want a local lock cache context
849
1010
        if not self._lock_mode:
 
1011
            self._note_lock('r')
850
1012
            self._lock_mode = 'r'
851
1013
            self._lock_count = 1
852
1014
            self._unstacked_provider.enable_cache(cache_misses=True)
853
1015
            if self._real_repository is not None:
854
1016
                self._real_repository.lock_read()
 
1017
            for repo in self._fallback_repositories:
 
1018
                repo.lock_read()
855
1019
        else:
856
1020
            self._lock_count += 1
 
1021
        return lock.LogicalLockResult(self.unlock)
857
1022
 
858
1023
    def _remote_lock_write(self, token):
859
1024
        path = self.bzrdir._path_for_remote_call(self._client)
870
1035
 
871
1036
    def lock_write(self, token=None, _skip_rpc=False):
872
1037
        if not self._lock_mode:
 
1038
            self._note_lock('w')
873
1039
            if _skip_rpc:
874
1040
                if self._lock_token is not None:
875
1041
                    if token != self._lock_token:
889
1055
                self._leave_lock = False
890
1056
            self._lock_mode = 'w'
891
1057
            self._lock_count = 1
892
 
            self._unstacked_provider.enable_cache(cache_misses=False)
 
1058
            cache_misses = self._real_repository is None
 
1059
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
 
1060
            for repo in self._fallback_repositories:
 
1061
                # Writes don't affect fallback repos
 
1062
                repo.lock_read()
893
1063
        elif self._lock_mode == 'r':
894
1064
            raise errors.ReadOnlyError(self)
895
1065
        else:
896
1066
            self._lock_count += 1
897
 
        return self._lock_token or None
 
1067
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
898
1068
 
899
1069
    def leave_lock_in_place(self):
900
1070
        if not self._lock_token:
921
1091
        if isinstance(repository, RemoteRepository):
922
1092
            raise AssertionError()
923
1093
        self._real_repository = repository
924
 
        # If the _real_repository has _fallback_repositories, clear them out,
925
 
        # because we want it to have the same set as this repository.  This is
926
 
        # reasonable to do because the fallbacks we clear here are from a
927
 
        # "real" branch, and we're about to replace them with the equivalents
928
 
        # from a RemoteBranch.
929
 
        self._real_repository._fallback_repositories = []
 
1094
        # three code paths happen here:
 
1095
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
 
1096
        # up stacking. In this case self._fallback_repositories is [], and the
 
1097
        # real repo is already setup. Preserve the real repo and
 
1098
        # RemoteRepository.add_fallback_repository will avoid adding
 
1099
        # duplicates.
 
1100
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
 
1101
        # ensure_real is triggered from a branch, the real repository to
 
1102
        # set already has a matching list with separate instances, but
 
1103
        # as they are also RemoteRepositories we don't worry about making the
 
1104
        # lists be identical.
 
1105
        # 3) new servers, RemoteRepository.ensure_real is triggered before
 
1106
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
 
1107
        # and need to populate it.
 
1108
        if (self._fallback_repositories and
 
1109
            len(self._real_repository._fallback_repositories) !=
 
1110
            len(self._fallback_repositories)):
 
1111
            if len(self._real_repository._fallback_repositories):
 
1112
                raise AssertionError(
 
1113
                    "cannot cleanly remove existing _fallback_repositories")
930
1114
        for fb in self._fallback_repositories:
931
1115
            self._real_repository.add_fallback_repository(fb)
932
1116
        if self._lock_mode == 'w':
939
1123
    def start_write_group(self):
940
1124
        """Start a write group on the decorated repository.
941
1125
 
942
 
        Smart methods peform operations in a single step so this api
 
1126
        Smart methods perform operations in a single step so this API
943
1127
        is not really applicable except as a compatibility thunk
944
1128
        for older plugins that don't use e.g. the CommitBuilder
945
1129
        facility.
960
1144
        else:
961
1145
            raise errors.UnexpectedSmartServerResponse(response)
962
1146
 
 
1147
    @only_raises(errors.LockNotHeld, errors.LockBroken)
963
1148
    def unlock(self):
964
1149
        if not self._lock_count:
965
 
            raise errors.LockNotHeld(self)
 
1150
            return lock.cant_unlock_not_held(self)
966
1151
        self._lock_count -= 1
967
1152
        if self._lock_count > 0:
968
1153
            return
982
1167
            # problem releasing the vfs-based lock.
983
1168
            if old_mode == 'w':
984
1169
                # Only write-locked repositories need to make a remote method
985
 
                # call to perfom the unlock.
 
1170
                # call to perform the unlock.
986
1171
                old_token = self._lock_token
987
1172
                self._lock_token = None
988
1173
                if not self._leave_lock:
989
1174
                    self._unlock(old_token)
 
1175
        # Fallbacks are always 'lock_read()' so we don't pay attention to
 
1176
        # self._leave_lock
 
1177
        for repo in self._fallback_repositories:
 
1178
            repo.unlock()
990
1179
 
991
1180
    def break_lock(self):
992
1181
        # should hand off to the network
1056
1245
        # We need to accumulate additional repositories here, to pass them in
1057
1246
        # on various RPC's.
1058
1247
        #
 
1248
        if self.is_locked():
 
1249
            # We will call fallback.unlock() when we transition to the unlocked
 
1250
            # state, so always add a lock here. If a caller passes us a locked
 
1251
            # repository, they are responsible for unlocking it later.
 
1252
            repository.lock_read()
 
1253
        self._check_fallback_repository(repository)
1059
1254
        self._fallback_repositories.append(repository)
1060
1255
        # If self._real_repository was parameterised already (e.g. because a
1061
1256
        # _real_branch had its get_stacked_on_url method called), then the
1062
1257
        # repository to be added may already be in the _real_repositories list.
1063
1258
        if self._real_repository is not None:
1064
 
            if repository not in self._real_repository._fallback_repositories:
 
1259
            fallback_locations = [repo.user_url for repo in
 
1260
                self._real_repository._fallback_repositories]
 
1261
            if repository.user_url not in fallback_locations:
1065
1262
                self._real_repository.add_fallback_repository(repository)
1066
 
        else:
1067
 
            # They are also seen by the fallback repository.  If it doesn't
1068
 
            # exist yet they'll be added then.  This implicitly copies them.
1069
 
            self._ensure_real()
 
1263
 
 
1264
    def _check_fallback_repository(self, repository):
 
1265
        """Check that this repository can fallback to repository safely.
 
1266
 
 
1267
        Raise an error if not.
 
1268
 
 
1269
        :param repository: A repository to fallback to.
 
1270
        """
 
1271
        return _mod_repository.InterRepository._assert_same_model(
 
1272
            self, repository)
1070
1273
 
1071
1274
    def add_inventory(self, revid, inv, parents):
1072
1275
        self._ensure_real()
1073
1276
        return self._real_repository.add_inventory(revid, inv, parents)
1074
1277
 
1075
1278
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1076
 
                               parents):
 
1279
            parents, basis_inv=None, propagate_caches=False):
1077
1280
        self._ensure_real()
1078
1281
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1079
 
            delta, new_revision_id, parents)
 
1282
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1283
            propagate_caches=propagate_caches)
1080
1284
 
1081
1285
    def add_revision(self, rev_id, rev, inv=None, config=None):
1082
1286
        self._ensure_real()
1088
1292
        self._ensure_real()
1089
1293
        return self._real_repository.get_inventory(revision_id)
1090
1294
 
1091
 
    def iter_inventories(self, revision_ids):
 
1295
    def iter_inventories(self, revision_ids, ordering=None):
1092
1296
        self._ensure_real()
1093
 
        return self._real_repository.iter_inventories(revision_ids)
 
1297
        return self._real_repository.iter_inventories(revision_ids, ordering)
1094
1298
 
1095
1299
    @needs_read_lock
1096
1300
    def get_revision(self, revision_id):
1112
1316
        return self._real_repository.make_working_trees()
1113
1317
 
1114
1318
    def refresh_data(self):
1115
 
        """Re-read any data needed to to synchronise with disk.
 
1319
        """Re-read any data needed to synchronise with disk.
1116
1320
 
1117
1321
        This method is intended to be called after another repository instance
1118
1322
        (such as one used by a smart server) has inserted data into the
1119
 
        repository. It may not be called during a write group, but may be
1120
 
        called at any other time.
 
1323
        repository. On all repositories this will work outside of write groups.
 
1324
        Some repository formats (pack and newer for bzrlib native formats)
 
1325
        support refresh_data inside write groups. If called inside a write
 
1326
        group on a repository that does not support refreshing in a write group
 
1327
        IsInWriteGroupError will be raised.
1121
1328
        """
1122
 
        if self.is_in_write_group():
1123
 
            raise errors.InternalBzrError(
1124
 
                "May not refresh_data while in a write group.")
1125
1329
        if self._real_repository is not None:
1126
1330
            self._real_repository.refresh_data()
1127
1331
 
1160
1364
            raise errors.InternalBzrError(
1161
1365
                "May not fetch while in a write group.")
1162
1366
        # fast path same-url fetch operations
1163
 
        if self.has_same_location(source) and fetch_spec is None:
 
1367
        if (self.has_same_location(source)
 
1368
            and fetch_spec is None
 
1369
            and self._has_same_fallbacks(source)):
1164
1370
            # check that last_revision is in 'from' and then return a
1165
1371
            # no-operation.
1166
1372
            if (revision_id is not None and
1215
1421
            # in one go, and the user probably will have seen a warning about
1216
1422
            # the server being old anyhow.
1217
1423
            rg = self._get_revision_graph(None)
1218
 
            # There is an api discrepency between get_parent_map and
 
1424
            # There is an API discrepancy between get_parent_map and
1219
1425
            # get_revision_graph. Specifically, a "key:()" pair in
1220
1426
            # get_revision_graph just means a node has no parents. For
1221
1427
            # "get_parent_map" it means the node is a ghost. So fix up the
1271
1477
        # We don't need to send ghosts back to the server as a position to
1272
1478
        # stop either.
1273
1479
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1480
        key_count = len(parents_map)
 
1481
        if (NULL_REVISION in result_parents
 
1482
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1483
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1484
            # in our cache of "missing" keys we need to increment our key count
 
1485
            # by 1, because the reconsitituted SearchResult on the server will
 
1486
            # still consider NULL_REVISION to be an included key.
 
1487
            key_count += 1
1274
1488
        included_keys = start_set.intersection(result_parents)
1275
1489
        start_set.difference_update(included_keys)
1276
 
        recipe = ('manual', start_set, stop_keys, len(parents_map))
 
1490
        recipe = ('manual', start_set, stop_keys, key_count)
1277
1491
        body = self._serialise_search_recipe(recipe)
1278
1492
        path = self.bzrdir._path_for_remote_call(self._client)
1279
1493
        for key in keys:
1331
1545
        return self._real_repository.get_signature_text(revision_id)
1332
1546
 
1333
1547
    @needs_read_lock
1334
 
    def get_inventory_xml(self, revision_id):
1335
 
        self._ensure_real()
1336
 
        return self._real_repository.get_inventory_xml(revision_id)
1337
 
 
1338
 
    def deserialise_inventory(self, revision_id, xml):
1339
 
        self._ensure_real()
1340
 
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
1548
    def _get_inventory_xml(self, revision_id):
 
1549
        self._ensure_real()
 
1550
        return self._real_repository._get_inventory_xml(revision_id)
1341
1551
 
1342
1552
    def reconcile(self, other=None, thorough=False):
1343
1553
        self._ensure_real()
1370
1580
        return self._real_repository.get_revision_reconcile(revision_id)
1371
1581
 
1372
1582
    @needs_read_lock
1373
 
    def check(self, revision_ids=None):
 
1583
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1374
1584
        self._ensure_real()
1375
 
        return self._real_repository.check(revision_ids=revision_ids)
 
1585
        return self._real_repository.check(revision_ids=revision_ids,
 
1586
            callback_refs=callback_refs, check_repo=check_repo)
1376
1587
 
1377
1588
    def copy_content_into(self, destination, revision_id=None):
1378
1589
        self._ensure_real()
1418
1629
        return self._real_repository.inventories
1419
1630
 
1420
1631
    @needs_write_lock
1421
 
    def pack(self):
 
1632
    def pack(self, hint=None, clean_obsolete_packs=False):
1422
1633
        """Compress the data within the repository.
1423
1634
 
1424
1635
        This is not currently implemented within the smart server.
1425
1636
        """
1426
1637
        self._ensure_real()
1427
 
        return self._real_repository.pack()
 
1638
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1428
1639
 
1429
1640
    @property
1430
1641
    def revisions(self):
1518
1729
        self._ensure_real()
1519
1730
        return self._real_repository.revision_graph_can_have_wrong_parents()
1520
1731
 
1521
 
    def _find_inconsistent_revision_parents(self):
 
1732
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1522
1733
        self._ensure_real()
1523
 
        return self._real_repository._find_inconsistent_revision_parents()
 
1734
        return self._real_repository._find_inconsistent_revision_parents(
 
1735
            revisions_iterator)
1524
1736
 
1525
1737
    def _check_for_inconsistent_revision_parents(self):
1526
1738
        self._ensure_real()
1532
1744
            providers.insert(0, other)
1533
1745
        providers.extend(r._make_parents_provider() for r in
1534
1746
                         self._fallback_repositories)
1535
 
        return graph._StackedParentsProvider(providers)
 
1747
        return graph.StackedParentsProvider(providers)
1536
1748
 
1537
1749
    def _serialise_search_recipe(self, recipe):
1538
1750
        """Serialise a graph search recipe.
1579
1791
 
1580
1792
    def insert_stream(self, stream, src_format, resume_tokens):
1581
1793
        target = self.target_repo
 
1794
        target._unstacked_provider.missing_keys.clear()
 
1795
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1582
1796
        if target._lock_token:
1583
 
            verb = 'Repository.insert_stream_locked'
1584
 
            extra_args = (target._lock_token or '',)
1585
 
            required_version = (1, 14)
 
1797
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1798
            lock_args = (target._lock_token or '',)
1586
1799
        else:
1587
 
            verb = 'Repository.insert_stream'
1588
 
            extra_args = ()
1589
 
            required_version = (1, 13)
 
1800
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
1801
            lock_args = ()
1590
1802
        client = target._client
1591
1803
        medium = client._medium
1592
 
        if medium._is_remote_before(required_version):
1593
 
            # No possible way this can work.
1594
 
            return self._insert_real(stream, src_format, resume_tokens)
1595
1804
        path = target.bzrdir._path_for_remote_call(client)
1596
 
        if not resume_tokens:
1597
 
            # XXX: Ugly but important for correctness, *will* be fixed during
1598
 
            # 1.13 cycle. Pushing a stream that is interrupted results in a
1599
 
            # fallback to the _real_repositories sink *with a partial stream*.
1600
 
            # Thats bad because we insert less data than bzr expected. To avoid
1601
 
            # this we do a trial push to make sure the verb is accessible, and
1602
 
            # do not fallback when actually pushing the stream. A cleanup patch
1603
 
            # is going to look at rewinding/restarting the stream/partial
1604
 
            # buffering etc.
 
1805
        # Probe for the verb to use with an empty stream before sending the
 
1806
        # real stream to it.  We do this both to avoid the risk of sending a
 
1807
        # large request that is then rejected, and because we don't want to
 
1808
        # implement a way to buffer, rewind, or restart the stream.
 
1809
        found_verb = False
 
1810
        for verb, required_version in candidate_calls:
 
1811
            if medium._is_remote_before(required_version):
 
1812
                continue
 
1813
            if resume_tokens:
 
1814
                # We've already done the probing (and set _is_remote_before) on
 
1815
                # a previous insert.
 
1816
                found_verb = True
 
1817
                break
1605
1818
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1606
1819
            try:
1607
1820
                response = client.call_with_body_stream(
1608
 
                    (verb, path, '') + extra_args, byte_stream)
 
1821
                    (verb, path, '') + lock_args, byte_stream)
1609
1822
            except errors.UnknownSmartMethod:
1610
1823
                medium._remember_remote_is_before(required_version)
1611
 
                return self._insert_real(stream, src_format, resume_tokens)
 
1824
            else:
 
1825
                found_verb = True
 
1826
                break
 
1827
        if not found_verb:
 
1828
            # Have to use VFS.
 
1829
            return self._insert_real(stream, src_format, resume_tokens)
 
1830
        self._last_inv_record = None
 
1831
        self._last_substream = None
 
1832
        if required_version < (1, 19):
 
1833
            # Remote side doesn't support inventory deltas.  Wrap the stream to
 
1834
            # make sure we don't send any.  If the stream contains inventory
 
1835
            # deltas we'll interrupt the smart insert_stream request and
 
1836
            # fallback to VFS.
 
1837
            stream = self._stop_stream_if_inventory_delta(stream)
1612
1838
        byte_stream = smart_repo._stream_to_byte_stream(
1613
1839
            stream, src_format)
1614
1840
        resume_tokens = ' '.join(resume_tokens)
1615
1841
        response = client.call_with_body_stream(
1616
 
            (verb, path, resume_tokens) + extra_args, byte_stream)
 
1842
            (verb, path, resume_tokens) + lock_args, byte_stream)
1617
1843
        if response[0][0] not in ('ok', 'missing-basis'):
1618
1844
            raise errors.UnexpectedSmartServerResponse(response)
 
1845
        if self._last_substream is not None:
 
1846
            # The stream included an inventory-delta record, but the remote
 
1847
            # side isn't new enough to support them.  So we need to send the
 
1848
            # rest of the stream via VFS.
 
1849
            self.target_repo.refresh_data()
 
1850
            return self._resume_stream_with_vfs(response, src_format)
1619
1851
        if response[0][0] == 'missing-basis':
1620
1852
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1621
1853
            resume_tokens = tokens
1622
 
            return resume_tokens, missing_keys
 
1854
            return resume_tokens, set(missing_keys)
1623
1855
        else:
1624
1856
            self.target_repo.refresh_data()
1625
1857
            return [], set()
1626
1858
 
 
1859
    def _resume_stream_with_vfs(self, response, src_format):
 
1860
        """Resume sending a stream via VFS, first resending the record and
 
1861
        substream that couldn't be sent via an insert_stream verb.
 
1862
        """
 
1863
        if response[0][0] == 'missing-basis':
 
1864
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1865
            # Ignore missing_keys, we haven't finished inserting yet
 
1866
        else:
 
1867
            tokens = []
 
1868
        def resume_substream():
 
1869
            # Yield the substream that was interrupted.
 
1870
            for record in self._last_substream:
 
1871
                yield record
 
1872
            self._last_substream = None
 
1873
        def resume_stream():
 
1874
            # Finish sending the interrupted substream
 
1875
            yield ('inventory-deltas', resume_substream())
 
1876
            # Then simply continue sending the rest of the stream.
 
1877
            for substream_kind, substream in self._last_stream:
 
1878
                yield substream_kind, substream
 
1879
        return self._insert_real(resume_stream(), src_format, tokens)
 
1880
 
 
1881
    def _stop_stream_if_inventory_delta(self, stream):
 
1882
        """Normally this just lets the original stream pass-through unchanged.
 
1883
 
 
1884
        However if any 'inventory-deltas' substream occurs it will stop
 
1885
        streaming, and store the interrupted substream and stream in
 
1886
        self._last_substream and self._last_stream so that the stream can be
 
1887
        resumed by _resume_stream_with_vfs.
 
1888
        """
 
1889
                    
 
1890
        stream_iter = iter(stream)
 
1891
        for substream_kind, substream in stream_iter:
 
1892
            if substream_kind == 'inventory-deltas':
 
1893
                self._last_substream = substream
 
1894
                self._last_stream = stream_iter
 
1895
                return
 
1896
            else:
 
1897
                yield substream_kind, substream
 
1898
            
1627
1899
 
1628
1900
class RemoteStreamSource(repository.StreamSource):
1629
1901
    """Stream data from a remote server."""
1632
1904
        if (self.from_repository._fallback_repositories and
1633
1905
            self.to_format._fetch_order == 'topological'):
1634
1906
            return self._real_stream(self.from_repository, search)
1635
 
        return self.missing_parents_chain(search, [self.from_repository] +
1636
 
            self.from_repository._fallback_repositories)
 
1907
        sources = []
 
1908
        seen = set()
 
1909
        repos = [self.from_repository]
 
1910
        while repos:
 
1911
            repo = repos.pop(0)
 
1912
            if repo in seen:
 
1913
                continue
 
1914
            seen.add(repo)
 
1915
            repos.extend(repo._fallback_repositories)
 
1916
            sources.append(repo)
 
1917
        return self.missing_parents_chain(search, sources)
 
1918
 
 
1919
    def get_stream_for_missing_keys(self, missing_keys):
 
1920
        self.from_repository._ensure_real()
 
1921
        real_repo = self.from_repository._real_repository
 
1922
        real_source = real_repo._get_source(self.to_format)
 
1923
        return real_source.get_stream_for_missing_keys(missing_keys)
1637
1924
 
1638
1925
    def _real_stream(self, repo, search):
1639
1926
        """Get a stream for search from repo.
1646
1933
        """
1647
1934
        source = repo._get_source(self.to_format)
1648
1935
        if isinstance(source, RemoteStreamSource):
1649
 
            return repository.StreamSource.get_stream(source, search)
 
1936
            repo._ensure_real()
 
1937
            source = repo._real_repository._get_source(self.to_format)
1650
1938
        return source.get_stream(search)
1651
1939
 
1652
1940
    def _get_stream(self, repo, search):
1669
1957
            return self._real_stream(repo, search)
1670
1958
        client = repo._client
1671
1959
        medium = client._medium
1672
 
        if medium._is_remote_before((1, 13)):
1673
 
            # streaming was added in 1.13
1674
 
            return self._real_stream(repo, search)
1675
1960
        path = repo.bzrdir._path_for_remote_call(client)
1676
 
        try:
1677
 
            search_bytes = repo._serialise_search_result(search)
1678
 
            response = repo._call_with_body_bytes_expecting_body(
1679
 
                'Repository.get_stream',
1680
 
                (path, self.to_format.network_name()), search_bytes)
1681
 
            response_tuple, response_handler = response
1682
 
        except errors.UnknownSmartMethod:
1683
 
            medium._remember_remote_is_before((1,13))
 
1961
        search_bytes = repo._serialise_search_result(search)
 
1962
        args = (path, self.to_format.network_name())
 
1963
        candidate_verbs = [
 
1964
            ('Repository.get_stream_1.19', (1, 19)),
 
1965
            ('Repository.get_stream', (1, 13))]
 
1966
        found_verb = False
 
1967
        for verb, version in candidate_verbs:
 
1968
            if medium._is_remote_before(version):
 
1969
                continue
 
1970
            try:
 
1971
                response = repo._call_with_body_bytes_expecting_body(
 
1972
                    verb, args, search_bytes)
 
1973
            except errors.UnknownSmartMethod:
 
1974
                medium._remember_remote_is_before(version)
 
1975
            else:
 
1976
                response_tuple, response_handler = response
 
1977
                found_verb = True
 
1978
                break
 
1979
        if not found_verb:
1684
1980
            return self._real_stream(repo, search)
1685
1981
        if response_tuple[0] != 'ok':
1686
1982
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1687
1983
        byte_stream = response_handler.read_streamed_body()
1688
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
1984
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
1985
            self._record_counter)
1689
1986
        if src_format.network_name() != repo._format.network_name():
1690
1987
            raise AssertionError(
1691
1988
                "Mismatched RemoteRepository and stream src %r, %r" % (
1698
1995
        :param search: The overall search to satisfy with streams.
1699
1996
        :param sources: A list of Repository objects to query.
1700
1997
        """
1701
 
        self.serialiser = self.to_format._serializer
 
1998
        self.from_serialiser = self.from_repository._format._serializer
1702
1999
        self.seen_revs = set()
1703
2000
        self.referenced_revs = set()
1704
2001
        # If there are heads in the search, or the key count is > 0, we are not
1721
2018
    def missing_parents_rev_handler(self, substream):
1722
2019
        for content in substream:
1723
2020
            revision_bytes = content.get_bytes_as('fulltext')
1724
 
            revision = self.serialiser.read_revision_from_string(revision_bytes)
 
2021
            revision = self.from_serialiser.read_revision_from_string(
 
2022
                revision_bytes)
1725
2023
            self.seen_revs.add(content.key[-1])
1726
2024
            self.referenced_revs.update(revision.parent_ids)
1727
2025
            yield content
1766
2064
                self._network_name)
1767
2065
 
1768
2066
    def get_format_description(self):
1769
 
        return 'Remote BZR Branch'
 
2067
        self._ensure_real()
 
2068
        return 'Remote: ' + self._custom_format.get_format_description()
1770
2069
 
1771
2070
    def network_name(self):
1772
2071
        return self._network_name
1773
2072
 
1774
 
    def open(self, a_bzrdir, ignore_fallbacks=False):
1775
 
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
2073
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2074
        return a_bzrdir.open_branch(name=name, 
 
2075
            ignore_fallbacks=ignore_fallbacks)
1776
2076
 
1777
 
    def _vfs_initialize(self, a_bzrdir):
 
2077
    def _vfs_initialize(self, a_bzrdir, name):
1778
2078
        # Initialisation when using a local bzrdir object, or a non-vfs init
1779
2079
        # method is not available on the server.
1780
2080
        # self._custom_format is always set - the start of initialize ensures
1781
2081
        # that.
1782
2082
        if isinstance(a_bzrdir, RemoteBzrDir):
1783
2083
            a_bzrdir._ensure_real()
1784
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
2084
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2085
                name)
1785
2086
        else:
1786
2087
            # We assume the bzrdir is parameterised; it may not be.
1787
 
            result = self._custom_format.initialize(a_bzrdir)
 
2088
            result = self._custom_format.initialize(a_bzrdir, name)
1788
2089
        if (isinstance(a_bzrdir, RemoteBzrDir) and
1789
2090
            not isinstance(result, RemoteBranch)):
1790
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
2091
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
2092
                                  name=name)
1791
2093
        return result
1792
2094
 
1793
 
    def initialize(self, a_bzrdir):
 
2095
    def initialize(self, a_bzrdir, name=None):
1794
2096
        # 1) get the network name to use.
1795
2097
        if self._custom_format:
1796
2098
            network_name = self._custom_format.network_name()
1802
2104
            network_name = reference_format.network_name()
1803
2105
        # Being asked to create on a non RemoteBzrDir:
1804
2106
        if not isinstance(a_bzrdir, RemoteBzrDir):
1805
 
            return self._vfs_initialize(a_bzrdir)
 
2107
            return self._vfs_initialize(a_bzrdir, name=name)
1806
2108
        medium = a_bzrdir._client._medium
1807
2109
        if medium._is_remote_before((1, 13)):
1808
 
            return self._vfs_initialize(a_bzrdir)
 
2110
            return self._vfs_initialize(a_bzrdir, name=name)
1809
2111
        # Creating on a remote bzr dir.
1810
2112
        # 2) try direct creation via RPC
1811
2113
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2114
        if name is not None:
 
2115
            # XXX JRV20100304: Support creating colocated branches
 
2116
            raise errors.NoColocatedBranchSupport(self)
1812
2117
        verb = 'BzrDir.create_branch'
1813
2118
        try:
1814
2119
            response = a_bzrdir._call(verb, path, network_name)
1815
2120
        except errors.UnknownSmartMethod:
1816
2121
            # Fallback - use vfs methods
1817
2122
            medium._remember_remote_is_before((1, 13))
1818
 
            return self._vfs_initialize(a_bzrdir)
 
2123
            return self._vfs_initialize(a_bzrdir, name=name)
1819
2124
        if response[0] != 'ok':
1820
2125
            raise errors.UnexpectedSmartServerResponse(response)
1821
2126
        # Turn the response into a RemoteRepository object.
1829
2134
                a_bzrdir._client)
1830
2135
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1831
2136
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1832
 
            format=format, setup_stacking=False)
 
2137
            format=format, setup_stacking=False, name=name)
1833
2138
        # XXX: We know this is a new branch, so it must have revno 0, revid
1834
2139
        # NULL_REVISION. Creating the branch locked would make this be unable
1835
2140
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1850
2155
        self._ensure_real()
1851
2156
        return self._custom_format.supports_stacking()
1852
2157
 
1853
 
 
1854
 
class RemoteBranch(branch.Branch, _RpcHelper):
 
2158
    def supports_set_append_revisions_only(self):
 
2159
        self._ensure_real()
 
2160
        return self._custom_format.supports_set_append_revisions_only()
 
2161
 
 
2162
 
 
2163
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1855
2164
    """Branch stored on a server accessed by HPSS RPC.
1856
2165
 
1857
2166
    At the moment most operations are mapped down to simple file operations.
1858
2167
    """
1859
2168
 
1860
2169
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1861
 
        _client=None, format=None, setup_stacking=True):
 
2170
        _client=None, format=None, setup_stacking=True, name=None):
1862
2171
        """Create a RemoteBranch instance.
1863
2172
 
1864
2173
        :param real_branch: An optional local implementation of the branch
1870
2179
        :param setup_stacking: If True make an RPC call to determine the
1871
2180
            stacked (or not) status of the branch. If False assume the branch
1872
2181
            is not stacked.
 
2182
        :param name: Colocated branch name
1873
2183
        """
1874
2184
        # We intentionally don't call the parent class's __init__, because it
1875
2185
        # will try to assign to self.tags, which is a property in this subclass.
1876
2186
        # And the parent's __init__ doesn't do much anyway.
1877
 
        self._revision_id_to_revno_cache = None
1878
 
        self._partial_revision_id_to_revno_cache = {}
1879
 
        self._revision_history_cache = None
1880
 
        self._last_revision_info_cache = None
1881
 
        self._merge_sorted_revisions_cache = None
1882
2187
        self.bzrdir = remote_bzrdir
1883
2188
        if _client is not None:
1884
2189
            self._client = _client
1897
2202
            self._real_branch.repository = self.repository
1898
2203
        else:
1899
2204
            self._real_branch = None
1900
 
        # Fill out expected attributes of branch for bzrlib api users.
1901
 
        self.base = self.bzrdir.root_transport.base
 
2205
        # Fill out expected attributes of branch for bzrlib API users.
 
2206
        self._clear_cached_state()
 
2207
        # TODO: deprecate self.base in favor of user_url
 
2208
        self.base = self.bzrdir.user_url
 
2209
        self._name = name
1902
2210
        self._control_files = None
1903
2211
        self._lock_mode = None
1904
2212
        self._lock_token = None
1915
2223
                    self._real_branch._format.network_name()
1916
2224
        else:
1917
2225
            self._format = format
 
2226
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
 
2227
        # branch.open_branch method.
 
2228
        self._real_ignore_fallbacks = not setup_stacking
1918
2229
        if not self._format._network_name:
1919
2230
            # Did not get from open_branchV2 - old server.
1920
2231
            self._ensure_real()
1925
2236
        hooks = branch.Branch.hooks['open']
1926
2237
        for hook in hooks:
1927
2238
            hook(self)
 
2239
        self._is_stacked = False
1928
2240
        if setup_stacking:
1929
2241
            self._setup_stacking()
1930
2242
 
1936
2248
        except (errors.NotStacked, errors.UnstackableBranchFormat,
1937
2249
            errors.UnstackableRepositoryFormat), e:
1938
2250
            return
1939
 
        # it's relative to this branch...
1940
 
        fallback_url = urlutils.join(self.base, fallback_url)
1941
 
        transports = [self.bzrdir.root_transport]
1942
 
        stacked_on = branch.Branch.open(fallback_url,
1943
 
                                        possible_transports=transports)
1944
 
        self.repository.add_fallback_repository(stacked_on.repository)
 
2251
        self._is_stacked = True
 
2252
        self._activate_fallback_location(fallback_url)
 
2253
 
 
2254
    def _get_config(self):
 
2255
        return RemoteBranchConfig(self)
1945
2256
 
1946
2257
    def _get_real_transport(self):
1947
2258
        # if we try vfs access, return the real branch's vfs transport
1965
2276
                raise AssertionError('smart server vfs must be enabled '
1966
2277
                    'to use vfs implementation')
1967
2278
            self.bzrdir._ensure_real()
1968
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
2279
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
2280
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
1969
2281
            if self.repository._real_repository is None:
1970
2282
                # Give the remote repository the matching real repo.
1971
2283
                real_repo = self._real_branch.repository
2045
2357
            raise errors.UnexpectedSmartServerResponse(response)
2046
2358
        return response[1]
2047
2359
 
 
2360
    def set_stacked_on_url(self, url):
 
2361
        branch.Branch.set_stacked_on_url(self, url)
 
2362
        if not url:
 
2363
            self._is_stacked = False
 
2364
        else:
 
2365
            self._is_stacked = True
 
2366
        
2048
2367
    def _vfs_get_tags_bytes(self):
2049
2368
        self._ensure_real()
2050
2369
        return self._real_branch._get_tags_bytes()
2060
2379
            return self._vfs_get_tags_bytes()
2061
2380
        return response[0]
2062
2381
 
 
2382
    def _vfs_set_tags_bytes(self, bytes):
 
2383
        self._ensure_real()
 
2384
        return self._real_branch._set_tags_bytes(bytes)
 
2385
 
 
2386
    def _set_tags_bytes(self, bytes):
 
2387
        medium = self._client._medium
 
2388
        if medium._is_remote_before((1, 18)):
 
2389
            self._vfs_set_tags_bytes(bytes)
 
2390
            return
 
2391
        try:
 
2392
            args = (
 
2393
                self._remote_path(), self._lock_token, self._repo_lock_token)
 
2394
            response = self._call_with_body_bytes(
 
2395
                'Branch.set_tags_bytes', args, bytes)
 
2396
        except errors.UnknownSmartMethod:
 
2397
            medium._remember_remote_is_before((1, 18))
 
2398
            self._vfs_set_tags_bytes(bytes)
 
2399
 
2063
2400
    def lock_read(self):
 
2401
        """Lock the branch for read operations.
 
2402
 
 
2403
        :return: A bzrlib.lock.LogicalLockResult.
 
2404
        """
2064
2405
        self.repository.lock_read()
2065
2406
        if not self._lock_mode:
 
2407
            self._note_lock('r')
2066
2408
            self._lock_mode = 'r'
2067
2409
            self._lock_count = 1
2068
2410
            if self._real_branch is not None:
2069
2411
                self._real_branch.lock_read()
2070
2412
        else:
2071
2413
            self._lock_count += 1
 
2414
        return lock.LogicalLockResult(self.unlock)
2072
2415
 
2073
2416
    def _remote_lock_write(self, token):
2074
2417
        if token is None:
2075
2418
            branch_token = repo_token = ''
2076
2419
        else:
2077
2420
            branch_token = token
2078
 
            repo_token = self.repository.lock_write()
 
2421
            repo_token = self.repository.lock_write().repository_token
2079
2422
            self.repository.unlock()
2080
2423
        err_context = {'token': token}
2081
 
        response = self._call(
2082
 
            'Branch.lock_write', self._remote_path(), branch_token,
2083
 
            repo_token or '', **err_context)
 
2424
        try:
 
2425
            response = self._call(
 
2426
                'Branch.lock_write', self._remote_path(), branch_token,
 
2427
                repo_token or '', **err_context)
 
2428
        except errors.LockContention, e:
 
2429
            # The LockContention from the server doesn't have any
 
2430
            # information about the lock_url. We re-raise LockContention
 
2431
            # with valid lock_url.
 
2432
            raise errors.LockContention('(remote lock)',
 
2433
                self.repository.base.split('.bzr/')[0])
2084
2434
        if response[0] != 'ok':
2085
2435
            raise errors.UnexpectedSmartServerResponse(response)
2086
2436
        ok, branch_token, repo_token = response
2088
2438
 
2089
2439
    def lock_write(self, token=None):
2090
2440
        if not self._lock_mode:
 
2441
            self._note_lock('w')
2091
2442
            # Lock the branch and repo in one remote call.
2092
2443
            remote_tokens = self._remote_lock_write(token)
2093
2444
            self._lock_token, self._repo_lock_token = remote_tokens
2106
2457
            self._lock_mode = 'w'
2107
2458
            self._lock_count = 1
2108
2459
        elif self._lock_mode == 'r':
2109
 
            raise errors.ReadOnlyTransaction
 
2460
            raise errors.ReadOnlyError(self)
2110
2461
        else:
2111
2462
            if token is not None:
2112
2463
                # A token was given to lock_write, and we're relocking, so
2117
2468
            self._lock_count += 1
2118
2469
            # Re-lock the repository too.
2119
2470
            self.repository.lock_write(self._repo_lock_token)
2120
 
        return self._lock_token or None
2121
 
 
2122
 
    def _set_tags_bytes(self, bytes):
2123
 
        self._ensure_real()
2124
 
        return self._real_branch._set_tags_bytes(bytes)
 
2471
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2125
2472
 
2126
2473
    def _unlock(self, branch_token, repo_token):
2127
2474
        err_context = {'token': str((branch_token, repo_token))}
2132
2479
            return
2133
2480
        raise errors.UnexpectedSmartServerResponse(response)
2134
2481
 
 
2482
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2135
2483
    def unlock(self):
2136
2484
        try:
2137
2485
            self._lock_count -= 1
2150
2498
                    self._real_branch.unlock()
2151
2499
                if mode != 'w':
2152
2500
                    # Only write-locked branched need to make a remote method
2153
 
                    # call to perfom the unlock.
 
2501
                    # call to perform the unlock.
2154
2502
                    return
2155
2503
                if not self._lock_token:
2156
2504
                    raise AssertionError('Locked, but no token!')
2177
2525
            raise NotImplementedError(self.dont_leave_lock_in_place)
2178
2526
        self._leave_lock = False
2179
2527
 
 
2528
    @needs_read_lock
 
2529
    def get_rev_id(self, revno, history=None):
 
2530
        if revno == 0:
 
2531
            return _mod_revision.NULL_REVISION
 
2532
        last_revision_info = self.last_revision_info()
 
2533
        ok, result = self.repository.get_rev_id_for_revno(
 
2534
            revno, last_revision_info)
 
2535
        if ok:
 
2536
            return result
 
2537
        missing_parent = result[1]
 
2538
        # Either the revision named by the server is missing, or its parent
 
2539
        # is.  Call get_parent_map to determine which, so that we report a
 
2540
        # useful error.
 
2541
        parent_map = self.repository.get_parent_map([missing_parent])
 
2542
        if missing_parent in parent_map:
 
2543
            missing_parent = parent_map[missing_parent]
 
2544
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
2545
 
2180
2546
    def _last_revision_info(self):
2181
2547
        response = self._call('Branch.last_revision_info', self._remote_path())
2182
2548
        if response[0] != 'ok':
2187
2553
 
2188
2554
    def _gen_revision_history(self):
2189
2555
        """See Branch._gen_revision_history()."""
 
2556
        if self._is_stacked:
 
2557
            self._ensure_real()
 
2558
            return self._real_branch._gen_revision_history()
2190
2559
        response_tuple, response_handler = self._call_expecting_body(
2191
2560
            'Branch.revision_history', self._remote_path())
2192
2561
        if response_tuple[0] != 'ok':
2277
2646
        self._ensure_real()
2278
2647
        return self._real_branch._get_parent_location()
2279
2648
 
2280
 
    def set_parent(self, url):
2281
 
        self._ensure_real()
2282
 
        return self._real_branch.set_parent(url)
2283
 
 
2284
2649
    def _set_parent_location(self, url):
2285
 
        # Used by tests, to poke bad urls into branch configurations
2286
 
        if url is None:
2287
 
            self.set_parent(url)
2288
 
        else:
2289
 
            self._ensure_real()
2290
 
            return self._real_branch._set_parent_location(url)
2291
 
 
2292
 
    def set_stacked_on_url(self, stacked_location):
2293
 
        """Set the URL this branch is stacked against.
2294
 
 
2295
 
        :raises UnstackableBranchFormat: If the branch does not support
2296
 
            stacking.
2297
 
        :raises UnstackableRepositoryFormat: If the repository does not support
2298
 
            stacking.
2299
 
        """
 
2650
        medium = self._client._medium
 
2651
        if medium._is_remote_before((1, 15)):
 
2652
            return self._vfs_set_parent_location(url)
 
2653
        try:
 
2654
            call_url = url or ''
 
2655
            if type(call_url) is not str:
 
2656
                raise AssertionError('url must be a str or None (%s)' % url)
 
2657
            response = self._call('Branch.set_parent_location',
 
2658
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2659
                call_url)
 
2660
        except errors.UnknownSmartMethod:
 
2661
            medium._remember_remote_is_before((1, 15))
 
2662
            return self._vfs_set_parent_location(url)
 
2663
        if response != ():
 
2664
            raise errors.UnexpectedSmartServerResponse(response)
 
2665
 
 
2666
    def _vfs_set_parent_location(self, url):
2300
2667
        self._ensure_real()
2301
 
        return self._real_branch.set_stacked_on_url(stacked_location)
 
2668
        return self._real_branch._set_parent_location(url)
2302
2669
 
2303
2670
    @needs_write_lock
2304
2671
    def pull(self, source, overwrite=False, stop_revision=None,
2372
2739
        return self._real_branch.set_push_location(location)
2373
2740
 
2374
2741
 
 
2742
class RemoteConfig(object):
 
2743
    """A Config that reads and writes from smart verbs.
 
2744
 
 
2745
    It is a low-level object that considers config data to be name/value pairs
 
2746
    that may be associated with a section. Assigning meaning to the these
 
2747
    values is done at higher levels like bzrlib.config.TreeConfig.
 
2748
    """
 
2749
 
 
2750
    def get_option(self, name, section=None, default=None):
 
2751
        """Return the value associated with a named option.
 
2752
 
 
2753
        :param name: The name of the value
 
2754
        :param section: The section the option is in (if any)
 
2755
        :param default: The value to return if the value is not set
 
2756
        :return: The value or default value
 
2757
        """
 
2758
        try:
 
2759
            configobj = self._get_configobj()
 
2760
            if section is None:
 
2761
                section_obj = configobj
 
2762
            else:
 
2763
                try:
 
2764
                    section_obj = configobj[section]
 
2765
                except KeyError:
 
2766
                    return default
 
2767
            return section_obj.get(name, default)
 
2768
        except errors.UnknownSmartMethod:
 
2769
            return self._vfs_get_option(name, section, default)
 
2770
 
 
2771
    def _response_to_configobj(self, response):
 
2772
        if len(response[0]) and response[0][0] != 'ok':
 
2773
            raise errors.UnexpectedSmartServerResponse(response)
 
2774
        lines = response[1].read_body_bytes().splitlines()
 
2775
        return config.ConfigObj(lines, encoding='utf-8')
 
2776
 
 
2777
 
 
2778
class RemoteBranchConfig(RemoteConfig):
 
2779
    """A RemoteConfig for Branches."""
 
2780
 
 
2781
    def __init__(self, branch):
 
2782
        self._branch = branch
 
2783
 
 
2784
    def _get_configobj(self):
 
2785
        path = self._branch._remote_path()
 
2786
        response = self._branch._client.call_expecting_body(
 
2787
            'Branch.get_config_file', path)
 
2788
        return self._response_to_configobj(response)
 
2789
 
 
2790
    def set_option(self, value, name, section=None):
 
2791
        """Set the value associated with a named option.
 
2792
 
 
2793
        :param value: The value to set
 
2794
        :param name: The name of the value to set
 
2795
        :param section: The section the option is in (if any)
 
2796
        """
 
2797
        medium = self._branch._client._medium
 
2798
        if medium._is_remote_before((1, 14)):
 
2799
            return self._vfs_set_option(value, name, section)
 
2800
        if isinstance(value, dict):
 
2801
            if medium._is_remote_before((2, 2)):
 
2802
                return self._vfs_set_option(value, name, section)
 
2803
            return self._set_config_option_dict(value, name, section)
 
2804
        else:
 
2805
            return self._set_config_option(value, name, section)
 
2806
 
 
2807
    def _set_config_option(self, value, name, section):
 
2808
        try:
 
2809
            path = self._branch._remote_path()
 
2810
            response = self._branch._client.call('Branch.set_config_option',
 
2811
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2812
                value.encode('utf8'), name, section or '')
 
2813
        except errors.UnknownSmartMethod:
 
2814
            medium = self._branch._client._medium
 
2815
            medium._remember_remote_is_before((1, 14))
 
2816
            return self._vfs_set_option(value, name, section)
 
2817
        if response != ():
 
2818
            raise errors.UnexpectedSmartServerResponse(response)
 
2819
 
 
2820
    def _serialize_option_dict(self, option_dict):
 
2821
        utf8_dict = {}
 
2822
        for key, value in option_dict.items():
 
2823
            if isinstance(key, unicode):
 
2824
                key = key.encode('utf8')
 
2825
            if isinstance(value, unicode):
 
2826
                value = value.encode('utf8')
 
2827
            utf8_dict[key] = value
 
2828
        return bencode.bencode(utf8_dict)
 
2829
 
 
2830
    def _set_config_option_dict(self, value, name, section):
 
2831
        try:
 
2832
            path = self._branch._remote_path()
 
2833
            serialised_dict = self._serialize_option_dict(value)
 
2834
            response = self._branch._client.call(
 
2835
                'Branch.set_config_option_dict',
 
2836
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2837
                serialised_dict, name, section or '')
 
2838
        except errors.UnknownSmartMethod:
 
2839
            medium = self._branch._client._medium
 
2840
            medium._remember_remote_is_before((2, 2))
 
2841
            return self._vfs_set_option(value, name, section)
 
2842
        if response != ():
 
2843
            raise errors.UnexpectedSmartServerResponse(response)
 
2844
 
 
2845
    def _real_object(self):
 
2846
        self._branch._ensure_real()
 
2847
        return self._branch._real_branch
 
2848
 
 
2849
    def _vfs_set_option(self, value, name, section=None):
 
2850
        return self._real_object()._get_config().set_option(
 
2851
            value, name, section)
 
2852
 
 
2853
 
 
2854
class RemoteBzrDirConfig(RemoteConfig):
 
2855
    """A RemoteConfig for BzrDirs."""
 
2856
 
 
2857
    def __init__(self, bzrdir):
 
2858
        self._bzrdir = bzrdir
 
2859
 
 
2860
    def _get_configobj(self):
 
2861
        medium = self._bzrdir._client._medium
 
2862
        verb = 'BzrDir.get_config_file'
 
2863
        if medium._is_remote_before((1, 15)):
 
2864
            raise errors.UnknownSmartMethod(verb)
 
2865
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
 
2866
        response = self._bzrdir._call_expecting_body(
 
2867
            verb, path)
 
2868
        return self._response_to_configobj(response)
 
2869
 
 
2870
    def _vfs_get_option(self, name, section, default):
 
2871
        return self._real_object()._get_config().get_option(
 
2872
            name, section, default)
 
2873
 
 
2874
    def set_option(self, value, name, section=None):
 
2875
        """Set the value associated with a named option.
 
2876
 
 
2877
        :param value: The value to set
 
2878
        :param name: The name of the value to set
 
2879
        :param section: The section the option is in (if any)
 
2880
        """
 
2881
        return self._real_object()._get_config().set_option(
 
2882
            value, name, section)
 
2883
 
 
2884
    def _real_object(self):
 
2885
        self._bzrdir._ensure_real()
 
2886
        return self._bzrdir._real_bzrdir
 
2887
 
 
2888
 
 
2889
 
2375
2890
def _extract_tar(tar, to_dir):
2376
2891
    """Extract all the contents of a tarfile object.
2377
2892
 
2415
2930
                    'Missing key %r in context %r', key_err.args[0], context)
2416
2931
                raise err
2417
2932
 
2418
 
    if err.error_verb == 'NoSuchRevision':
 
2933
    if err.error_verb == 'IncompatibleRepositories':
 
2934
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2935
            err.error_args[1], err.error_args[2])
 
2936
    elif err.error_verb == 'NoSuchRevision':
2419
2937
        raise NoSuchRevision(find('branch'), err.error_args[0])
2420
2938
    elif err.error_verb == 'nosuchrevision':
2421
2939
        raise NoSuchRevision(find('repository'), err.error_args[0])
2422
 
    elif err.error_tuple == ('nobranch',):
2423
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2940
    elif err.error_verb == 'nobranch':
 
2941
        if len(err.error_args) >= 1:
 
2942
            extra = err.error_args[0]
 
2943
        else:
 
2944
            extra = None
 
2945
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2946
            detail=extra)
2424
2947
    elif err.error_verb == 'norepository':
2425
2948
        raise errors.NoRepositoryPresent(find('bzrdir'))
2426
2949
    elif err.error_verb == 'LockContention':