~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-19 17:53:37 UTC
  • mto: This revision was merged to the branch mainline in revision 4466.
  • Revision ID: john@arbash-meinel.com-20090619175337-uozt3bntdd48lh4z
Update time_graph to use X:1 ratios rather than 0.xxx ratios.
It is just easier to track now that the new code is much faster.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
 
17
20
import bz2
18
21
 
19
22
from bzrlib import (
21
24
    branch,
22
25
    bzrdir,
23
26
    config,
24
 
    controldir,
25
27
    debug,
26
28
    errors,
27
29
    graph,
28
 
    lock,
29
30
    lockdir,
30
31
    repository,
31
 
    repository as _mod_repository,
32
32
    revision,
33
33
    revision as _mod_revision,
34
 
    static_tuple,
35
34
    symbol_versioning,
36
35
)
37
 
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
36
from bzrlib.branch import BranchReferenceFormat
38
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
39
 
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
40
39
from bzrlib.errors import (
41
40
    NoSuchRevision,
42
41
    SmartProtocolError,
44
43
from bzrlib.lockable_files import LockableFiles
45
44
from bzrlib.smart import client, vfs, repository as smart_repo
46
45
from bzrlib.revision import ensure_null, NULL_REVISION
47
 
from bzrlib.repository import RepositoryWriteLockResult
48
46
from bzrlib.trace import mutter, note, warning
49
47
 
50
48
 
63
61
        except errors.ErrorFromSmartServer, err:
64
62
            self._translate_error(err, **err_context)
65
63
 
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
 
 
72
64
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
65
                                             **err_context):
74
66
        try:
93
85
class RemoteBzrDir(BzrDir, _RpcHelper):
94
86
    """Control directory on a remote server, accessed via bzr:// or similar."""
95
87
 
96
 
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
88
    def __init__(self, transport, format, _client=None):
97
89
        """Construct a RemoteBzrDir.
98
90
 
99
91
        :param _client: Private parameter for testing. Disables probing and the
103
95
        # this object holds a delegated bzrdir that uses file-level operations
104
96
        # to talk to the other side
105
97
        self._real_bzrdir = None
106
 
        self._has_working_tree = None
107
98
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
108
99
        # create_branch for details.
109
100
        self._next_open_branch_result = None
113
104
            self._client = client._SmartClient(medium)
114
105
        else:
115
106
            self._client = _client
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
 
107
            return
 
108
 
126
109
        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):
152
110
        response = self._call('BzrDir.open', path)
153
111
        if response not in [('yes',), ('no',)]:
154
112
            raise errors.UnexpectedSmartServerResponse(response)
155
113
        if response == ('no',):
156
 
            raise errors.NotBranchError(path=self.root_transport.base)
 
114
            raise errors.NotBranchError(path=transport.base)
157
115
 
158
116
    def _ensure_real(self):
159
117
        """Ensure that there is a _real_bzrdir set.
161
119
        Used before calls to self._real_bzrdir.
162
120
        """
163
121
        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()))
168
122
            self._real_bzrdir = BzrDir.open_from_transport(
169
123
                self.root_transport, _server_formats=False)
170
124
            self._format._network_name = \
214
168
        if len(branch_info) != 2:
215
169
            raise errors.UnexpectedSmartServerResponse(response)
216
170
        branch_ref, branch_name = branch_info
217
 
        format = controldir.network_format_registry.get(control_name)
 
171
        format = bzrdir.network_format_registry.get(control_name)
218
172
        if repo_name:
219
173
            format.repository_format = repository.network_format_registry.get(
220
174
                repo_name)
246
200
        self._ensure_real()
247
201
        self._real_bzrdir.destroy_repository()
248
202
 
249
 
    def create_branch(self, name=None):
 
203
    def create_branch(self):
250
204
        # as per meta1 formats - just delegate to the format object which may
251
205
        # be parameterised.
252
 
        real_branch = self._format.get_branch_format().initialize(self,
253
 
            name=name)
 
206
        real_branch = self._format.get_branch_format().initialize(self)
254
207
        if not isinstance(real_branch, RemoteBranch):
255
 
            result = RemoteBranch(self, self.find_repository(), real_branch,
256
 
                                  name=name)
 
208
            result = RemoteBranch(self, self.find_repository(), real_branch)
257
209
        else:
258
210
            result = real_branch
259
211
        # BzrDir.clone_on_transport() uses the result of create_branch but does
265
217
        self._next_open_branch_result = result
266
218
        return result
267
219
 
268
 
    def destroy_branch(self, name=None):
 
220
    def destroy_branch(self):
269
221
        """See BzrDir.destroy_branch"""
270
222
        self._ensure_real()
271
 
        self._real_bzrdir.destroy_branch(name=name)
 
223
        self._real_bzrdir.destroy_branch()
272
224
        self._next_open_branch_result = None
273
225
 
274
226
    def create_workingtree(self, revision_id=None, from_branch=None):
275
227
        raise errors.NotLocalUrl(self.transport.base)
276
228
 
277
 
    def find_branch_format(self, name=None):
 
229
    def find_branch_format(self):
278
230
        """Find the branch 'format' for this bzrdir.
279
231
 
280
232
        This might be a synthetic object for e.g. RemoteBranch and SVN.
281
233
        """
282
 
        b = self.open_branch(name=name)
 
234
        b = self.open_branch()
283
235
        return b._format
284
236
 
285
 
    def get_branch_reference(self, name=None):
 
237
    def get_branch_reference(self):
286
238
        """See BzrDir.get_branch_reference()."""
287
 
        if name is not None:
288
 
            # XXX JRV20100304: Support opening colocated branches
289
 
            raise errors.NoColocatedBranchSupport(self)
290
239
        response = self._get_branch_reference()
291
240
        if response[0] == 'ref':
292
241
            return response[1]
296
245
    def _get_branch_reference(self):
297
246
        path = self._path_for_remote_call(self._client)
298
247
        medium = self._client._medium
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
 
248
        if not medium._is_remote_before((1, 13)):
307
249
            try:
308
 
                response = self._call(verb, path)
 
250
                response = self._call('BzrDir.open_branchV2', path)
 
251
                if response[0] not in ('ref', 'branch'):
 
252
                    raise errors.UnexpectedSmartServerResponse(response)
 
253
                return response
309
254
            except errors.UnknownSmartMethod:
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'):
 
255
                medium._remember_remote_is_before((1, 13))
 
256
        response = self._call('BzrDir.open_branch', path)
 
257
        if response[0] != 'ok':
323
258
            raise errors.UnexpectedSmartServerResponse(response)
324
 
        return response
 
259
        if response[1] != '':
 
260
            return ('ref', response[1])
 
261
        else:
 
262
            return ('branch', '')
325
263
 
326
 
    def _get_tree_branch(self, name=None):
 
264
    def _get_tree_branch(self):
327
265
        """See BzrDir._get_tree_branch()."""
328
 
        return None, self.open_branch(name=name)
 
266
        return None, self.open_branch()
329
267
 
330
 
    def open_branch(self, name=None, unsupported=False,
331
 
                    ignore_fallbacks=False):
332
 
        if unsupported:
 
268
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
 
269
        if _unsupported:
333
270
            raise NotImplementedError('unsupported flag support not implemented yet.')
334
271
        if self._next_open_branch_result is not None:
335
272
            # See create_branch for details.
340
277
        if response[0] == 'ref':
341
278
            # a branch reference, use the existing BranchReference logic.
342
279
            format = BranchReferenceFormat()
343
 
            return format.open(self, name=name, _found=True,
344
 
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
280
            return format.open(self, _found=True, location=response[1],
 
281
                ignore_fallbacks=ignore_fallbacks)
345
282
        branch_format_name = response[1]
346
283
        if not branch_format_name:
347
284
            branch_format_name = None
348
285
        format = RemoteBranchFormat(network_name=branch_format_name)
349
286
        return RemoteBranch(self, self.find_repository(), format=format,
350
 
            setup_stacking=not ignore_fallbacks, name=name)
 
287
            setup_stacking=not ignore_fallbacks)
351
288
 
352
289
    def _open_repo_v1(self, path):
353
290
        verb = 'BzrDir.find_repository'
414
351
        else:
415
352
            raise errors.NoRepositoryPresent(self)
416
353
 
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
 
 
423
354
    def open_workingtree(self, recommend_upgrade=True):
424
 
        if self.has_workingtree():
 
355
        self._ensure_real()
 
356
        if self._real_bzrdir.has_workingtree():
425
357
            raise errors.NotLocalUrl(self.root_transport)
426
358
        else:
427
359
            raise errors.NoWorkingTree(self.root_transport.base)
430
362
        """Return the path to be used for this bzrdir in a remote call."""
431
363
        return client.remote_path_from_transport(self.root_transport)
432
364
 
433
 
    def get_branch_transport(self, branch_format, name=None):
 
365
    def get_branch_transport(self, branch_format):
434
366
        self._ensure_real()
435
 
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
367
        return self._real_bzrdir.get_branch_transport(branch_format)
436
368
 
437
369
    def get_repository_transport(self, repository_format):
438
370
        self._ensure_real()
490
422
        self._custom_format = None
491
423
        self._network_name = None
492
424
        self._creating_bzrdir = None
493
 
        self._supports_chks = None
494
425
        self._supports_external_lookups = None
495
426
        self._supports_tree_reference = None
496
427
        self._rich_root_data = None
497
428
 
498
 
    def __repr__(self):
499
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
500
 
            self._network_name)
501
 
 
502
429
    @property
503
430
    def fast_deltas(self):
504
431
        self._ensure_real()
512
439
        return self._rich_root_data
513
440
 
514
441
    @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
522
442
    def supports_external_lookups(self):
523
443
        if self._supports_external_lookups is None:
524
444
            self._ensure_real()
625
545
        return self._custom_format._fetch_reconcile
626
546
 
627
547
    def get_format_description(self):
628
 
        self._ensure_real()
629
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
548
        return 'bzr remote repository'
630
549
 
631
550
    def __eq__(self, other):
632
551
        return self.__class__ is other.__class__
633
552
 
 
553
    def check_conversion_target(self, target_format):
 
554
        if self.rich_root_data and not target_format.rich_root_data:
 
555
            raise errors.BadConversionTarget(
 
556
                'Does not support rich root data.', target_format)
 
557
        if (self.supports_tree_reference and
 
558
            not getattr(target_format, 'supports_tree_reference', False)):
 
559
            raise errors.BadConversionTarget(
 
560
                'Does not support nested trees', target_format)
 
561
 
634
562
    def network_name(self):
635
563
        if self._network_name:
636
564
            return self._network_name
638
566
        return self._creating_repo._real_repository._format.network_name()
639
567
 
640
568
    @property
641
 
    def pack_compresses(self):
642
 
        self._ensure_real()
643
 
        return self._custom_format.pack_compresses
644
 
 
645
 
    @property
646
569
    def _serializer(self):
647
570
        self._ensure_real()
648
571
        return self._custom_format._serializer
649
572
 
650
573
 
651
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
652
 
    controldir.ControlComponent):
 
574
class RemoteRepository(_RpcHelper):
653
575
    """Repository accessed over rpc.
654
576
 
655
577
    For the moment most operations are performed using local transport-backed
698
620
        # Additional places to query for data.
699
621
        self._fallback_repositories = []
700
622
 
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
 
        
712
623
    def __str__(self):
713
624
        return "%s(%s)" % (self.__class__.__name__, self.base)
714
625
 
807
718
        invocation. If in doubt chat to the bzr network team.
808
719
        """
809
720
        if self._real_repository is None:
810
 
            if 'hpssvfs' in debug.debug_flags:
 
721
            if 'hpss' in debug.debug_flags:
811
722
                import traceback
812
723
                warning('VFS Repository access triggered\n%s',
813
724
                    ''.join(traceback.format_stack()))
897
808
            result.add(_mod_revision.NULL_REVISION)
898
809
        return result
899
810
 
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
 
 
913
811
    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
917
812
        return (self.__class__ is other.__class__ and
918
813
                self.bzrdir.transport.base == other.bzrdir.transport.base)
919
814
 
922
817
        parents_provider = self._make_parents_provider(other_repository)
923
818
        return graph.Graph(parents_provider)
924
819
 
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
 
 
934
820
    def gather_stats(self, revid=None, committers=None):
935
821
        """See Repository.gather_stats()."""
936
822
        path = self.bzrdir._path_for_remote_call(self._client)
996
882
    def is_write_locked(self):
997
883
        return self._lock_mode == 'w'
998
884
 
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
 
 
1004
885
    def lock_read(self):
1005
 
        """Lock the repository for read operations.
1006
 
 
1007
 
        :return: A bzrlib.lock.LogicalLockResult.
1008
 
        """
1009
886
        # wrong eventually - want a local lock cache context
1010
887
        if not self._lock_mode:
1011
 
            self._note_lock('r')
1012
888
            self._lock_mode = 'r'
1013
889
            self._lock_count = 1
1014
890
            self._unstacked_provider.enable_cache(cache_misses=True)
1018
894
                repo.lock_read()
1019
895
        else:
1020
896
            self._lock_count += 1
1021
 
        return lock.LogicalLockResult(self.unlock)
1022
897
 
1023
898
    def _remote_lock_write(self, token):
1024
899
        path = self.bzrdir._path_for_remote_call(self._client)
1035
910
 
1036
911
    def lock_write(self, token=None, _skip_rpc=False):
1037
912
        if not self._lock_mode:
1038
 
            self._note_lock('w')
1039
913
            if _skip_rpc:
1040
914
                if self._lock_token is not None:
1041
915
                    if token != self._lock_token:
1064
938
            raise errors.ReadOnlyError(self)
1065
939
        else:
1066
940
            self._lock_count += 1
1067
 
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
941
        return self._lock_token or None
1068
942
 
1069
943
    def leave_lock_in_place(self):
1070
944
        if not self._lock_token:
1144
1018
        else:
1145
1019
            raise errors.UnexpectedSmartServerResponse(response)
1146
1020
 
1147
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1148
1021
    def unlock(self):
1149
1022
        if not self._lock_count:
1150
 
            return lock.cant_unlock_not_held(self)
 
1023
            raise errors.LockNotHeld(self)
1151
1024
        self._lock_count -= 1
1152
1025
        if self._lock_count > 0:
1153
1026
            return
1250
1123
            # state, so always add a lock here. If a caller passes us a locked
1251
1124
            # repository, they are responsible for unlocking it later.
1252
1125
            repository.lock_read()
1253
 
        self._check_fallback_repository(repository)
1254
1126
        self._fallback_repositories.append(repository)
1255
1127
        # If self._real_repository was parameterised already (e.g. because a
1256
1128
        # _real_branch had its get_stacked_on_url method called), then the
1257
1129
        # repository to be added may already be in the _real_repositories list.
1258
1130
        if self._real_repository is not None:
1259
 
            fallback_locations = [repo.user_url for repo in
 
1131
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
1260
1132
                self._real_repository._fallback_repositories]
1261
 
            if repository.user_url not in fallback_locations:
 
1133
            if repository.bzrdir.root_transport.base not in fallback_locations:
1262
1134
                self._real_repository.add_fallback_repository(repository)
1263
1135
 
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)
1273
 
 
1274
1136
    def add_inventory(self, revid, inv, parents):
1275
1137
        self._ensure_real()
1276
1138
        return self._real_repository.add_inventory(revid, inv, parents)
1277
1139
 
1278
1140
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1279
 
            parents, basis_inv=None, propagate_caches=False):
 
1141
                               parents):
1280
1142
        self._ensure_real()
1281
1143
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1282
 
            delta, new_revision_id, parents, basis_inv=basis_inv,
1283
 
            propagate_caches=propagate_caches)
 
1144
            delta, new_revision_id, parents)
1284
1145
 
1285
1146
    def add_revision(self, rev_id, rev, inv=None, config=None):
1286
1147
        self._ensure_real()
1292
1153
        self._ensure_real()
1293
1154
        return self._real_repository.get_inventory(revision_id)
1294
1155
 
1295
 
    def iter_inventories(self, revision_ids, ordering=None):
 
1156
    def iter_inventories(self, revision_ids):
1296
1157
        self._ensure_real()
1297
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1158
        return self._real_repository.iter_inventories(revision_ids)
1298
1159
 
1299
1160
    @needs_read_lock
1300
1161
    def get_revision(self, revision_id):
1316
1177
        return self._real_repository.make_working_trees()
1317
1178
 
1318
1179
    def refresh_data(self):
1319
 
        """Re-read any data needed to synchronise with disk.
 
1180
        """Re-read any data needed to to synchronise with disk.
1320
1181
 
1321
1182
        This method is intended to be called after another repository instance
1322
1183
        (such as one used by a smart server) has inserted data into the
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.
 
1184
        repository. It may not be called during a write group, but may be
 
1185
        called at any other time.
1328
1186
        """
 
1187
        if self.is_in_write_group():
 
1188
            raise errors.InternalBzrError(
 
1189
                "May not refresh_data while in a write group.")
1329
1190
        if self._real_repository is not None:
1330
1191
            self._real_repository.refresh_data()
1331
1192
 
1364
1225
            raise errors.InternalBzrError(
1365
1226
                "May not fetch while in a write group.")
1366
1227
        # fast path same-url fetch operations
1367
 
        if (self.has_same_location(source)
1368
 
            and fetch_spec is None
1369
 
            and self._has_same_fallbacks(source)):
 
1228
        if self.has_same_location(source) and fetch_spec is None:
1370
1229
            # check that last_revision is in 'from' and then return a
1371
1230
            # no-operation.
1372
1231
            if (revision_id is not None and
1545
1404
        return self._real_repository.get_signature_text(revision_id)
1546
1405
 
1547
1406
    @needs_read_lock
1548
 
    def _get_inventory_xml(self, revision_id):
1549
 
        self._ensure_real()
1550
 
        return self._real_repository._get_inventory_xml(revision_id)
 
1407
    def get_inventory_xml(self, revision_id):
 
1408
        self._ensure_real()
 
1409
        return self._real_repository.get_inventory_xml(revision_id)
 
1410
 
 
1411
    def deserialise_inventory(self, revision_id, xml):
 
1412
        self._ensure_real()
 
1413
        return self._real_repository.deserialise_inventory(revision_id, xml)
1551
1414
 
1552
1415
    def reconcile(self, other=None, thorough=False):
1553
1416
        self._ensure_real()
1580
1443
        return self._real_repository.get_revision_reconcile(revision_id)
1581
1444
 
1582
1445
    @needs_read_lock
1583
 
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
1446
    def check(self, revision_ids=None):
1584
1447
        self._ensure_real()
1585
 
        return self._real_repository.check(revision_ids=revision_ids,
1586
 
            callback_refs=callback_refs, check_repo=check_repo)
 
1448
        return self._real_repository.check(revision_ids=revision_ids)
1587
1449
 
1588
1450
    def copy_content_into(self, destination, revision_id=None):
1589
1451
        self._ensure_real()
1629
1491
        return self._real_repository.inventories
1630
1492
 
1631
1493
    @needs_write_lock
1632
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1494
    def pack(self):
1633
1495
        """Compress the data within the repository.
1634
1496
 
1635
1497
        This is not currently implemented within the smart server.
1636
1498
        """
1637
1499
        self._ensure_real()
1638
 
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
1500
        return self._real_repository.pack()
1639
1501
 
1640
1502
    @property
1641
1503
    def revisions(self):
1729
1591
        self._ensure_real()
1730
1592
        return self._real_repository.revision_graph_can_have_wrong_parents()
1731
1593
 
1732
 
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
 
1594
    def _find_inconsistent_revision_parents(self):
1733
1595
        self._ensure_real()
1734
 
        return self._real_repository._find_inconsistent_revision_parents(
1735
 
            revisions_iterator)
 
1596
        return self._real_repository._find_inconsistent_revision_parents()
1736
1597
 
1737
1598
    def _check_for_inconsistent_revision_parents(self):
1738
1599
        self._ensure_real()
1792
1653
    def insert_stream(self, stream, src_format, resume_tokens):
1793
1654
        target = self.target_repo
1794
1655
        target._unstacked_provider.missing_keys.clear()
1795
 
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1796
1656
        if target._lock_token:
1797
 
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1798
 
            lock_args = (target._lock_token or '',)
 
1657
            verb = 'Repository.insert_stream_locked'
 
1658
            extra_args = (target._lock_token or '',)
 
1659
            required_version = (1, 14)
1799
1660
        else:
1800
 
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
1801
 
            lock_args = ()
 
1661
            verb = 'Repository.insert_stream'
 
1662
            extra_args = ()
 
1663
            required_version = (1, 13)
1802
1664
        client = target._client
1803
1665
        medium = client._medium
 
1666
        if medium._is_remote_before(required_version):
 
1667
            # No possible way this can work.
 
1668
            return self._insert_real(stream, src_format, resume_tokens)
1804
1669
        path = target.bzrdir._path_for_remote_call(client)
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
 
1670
        if not resume_tokens:
 
1671
            # XXX: Ugly but important for correctness, *will* be fixed during
 
1672
            # 1.13 cycle. Pushing a stream that is interrupted results in a
 
1673
            # fallback to the _real_repositories sink *with a partial stream*.
 
1674
            # Thats bad because we insert less data than bzr expected. To avoid
 
1675
            # this we do a trial push to make sure the verb is accessible, and
 
1676
            # do not fallback when actually pushing the stream. A cleanup patch
 
1677
            # is going to look at rewinding/restarting the stream/partial
 
1678
            # buffering etc.
1818
1679
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1819
1680
            try:
1820
1681
                response = client.call_with_body_stream(
1821
 
                    (verb, path, '') + lock_args, byte_stream)
 
1682
                    (verb, path, '') + extra_args, byte_stream)
1822
1683
            except errors.UnknownSmartMethod:
1823
1684
                medium._remember_remote_is_before(required_version)
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)
 
1685
                return self._insert_real(stream, src_format, resume_tokens)
1838
1686
        byte_stream = smart_repo._stream_to_byte_stream(
1839
1687
            stream, src_format)
1840
1688
        resume_tokens = ' '.join(resume_tokens)
1841
1689
        response = client.call_with_body_stream(
1842
 
            (verb, path, resume_tokens) + lock_args, byte_stream)
 
1690
            (verb, path, resume_tokens) + extra_args, byte_stream)
1843
1691
        if response[0][0] not in ('ok', 'missing-basis'):
1844
1692
            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)
1851
1693
        if response[0][0] == 'missing-basis':
1852
1694
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1853
1695
            resume_tokens = tokens
1856
1698
            self.target_repo.refresh_data()
1857
1699
            return [], set()
1858
1700
 
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
 
            
1899
1701
 
1900
1702
class RemoteStreamSource(repository.StreamSource):
1901
1703
    """Stream data from a remote server."""
1904
1706
        if (self.from_repository._fallback_repositories and
1905
1707
            self.to_format._fetch_order == 'topological'):
1906
1708
            return self._real_stream(self.from_repository, search)
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)
 
1709
        return self.missing_parents_chain(search, [self.from_repository] +
 
1710
            self.from_repository._fallback_repositories)
1924
1711
 
1925
1712
    def _real_stream(self, repo, search):
1926
1713
        """Get a stream for search from repo.
1933
1720
        """
1934
1721
        source = repo._get_source(self.to_format)
1935
1722
        if isinstance(source, RemoteStreamSource):
1936
 
            repo._ensure_real()
1937
 
            source = repo._real_repository._get_source(self.to_format)
 
1723
            return repository.StreamSource.get_stream(source, search)
1938
1724
        return source.get_stream(search)
1939
1725
 
1940
1726
    def _get_stream(self, repo, search):
1957
1743
            return self._real_stream(repo, search)
1958
1744
        client = repo._client
1959
1745
        medium = client._medium
 
1746
        if medium._is_remote_before((1, 13)):
 
1747
            # streaming was added in 1.13
 
1748
            return self._real_stream(repo, search)
1960
1749
        path = repo.bzrdir._path_for_remote_call(client)
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:
 
1750
        try:
 
1751
            search_bytes = repo._serialise_search_result(search)
 
1752
            response = repo._call_with_body_bytes_expecting_body(
 
1753
                'Repository.get_stream',
 
1754
                (path, self.to_format.network_name()), search_bytes)
 
1755
            response_tuple, response_handler = response
 
1756
        except errors.UnknownSmartMethod:
 
1757
            medium._remember_remote_is_before((1,13))
1980
1758
            return self._real_stream(repo, search)
1981
1759
        if response_tuple[0] != 'ok':
1982
1760
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1983
1761
        byte_stream = response_handler.read_streamed_body()
1984
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
1985
 
            self._record_counter)
 
1762
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1986
1763
        if src_format.network_name() != repo._format.network_name():
1987
1764
            raise AssertionError(
1988
1765
                "Mismatched RemoteRepository and stream src %r, %r" % (
1995
1772
        :param search: The overall search to satisfy with streams.
1996
1773
        :param sources: A list of Repository objects to query.
1997
1774
        """
1998
 
        self.from_serialiser = self.from_repository._format._serializer
 
1775
        self.serialiser = self.to_format._serializer
1999
1776
        self.seen_revs = set()
2000
1777
        self.referenced_revs = set()
2001
1778
        # If there are heads in the search, or the key count is > 0, we are not
2018
1795
    def missing_parents_rev_handler(self, substream):
2019
1796
        for content in substream:
2020
1797
            revision_bytes = content.get_bytes_as('fulltext')
2021
 
            revision = self.from_serialiser.read_revision_from_string(
2022
 
                revision_bytes)
 
1798
            revision = self.serialiser.read_revision_from_string(revision_bytes)
2023
1799
            self.seen_revs.add(content.key[-1])
2024
1800
            self.referenced_revs.update(revision.parent_ids)
2025
1801
            yield content
2064
1840
                self._network_name)
2065
1841
 
2066
1842
    def get_format_description(self):
2067
 
        self._ensure_real()
2068
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
1843
        return 'Remote BZR Branch'
2069
1844
 
2070
1845
    def network_name(self):
2071
1846
        return self._network_name
2072
1847
 
2073
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2074
 
        return a_bzrdir.open_branch(name=name, 
2075
 
            ignore_fallbacks=ignore_fallbacks)
 
1848
    def open(self, a_bzrdir, ignore_fallbacks=False):
 
1849
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2076
1850
 
2077
 
    def _vfs_initialize(self, a_bzrdir, name):
 
1851
    def _vfs_initialize(self, a_bzrdir):
2078
1852
        # Initialisation when using a local bzrdir object, or a non-vfs init
2079
1853
        # method is not available on the server.
2080
1854
        # self._custom_format is always set - the start of initialize ensures
2081
1855
        # that.
2082
1856
        if isinstance(a_bzrdir, RemoteBzrDir):
2083
1857
            a_bzrdir._ensure_real()
2084
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2085
 
                name)
 
1858
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2086
1859
        else:
2087
1860
            # We assume the bzrdir is parameterised; it may not be.
2088
 
            result = self._custom_format.initialize(a_bzrdir, name)
 
1861
            result = self._custom_format.initialize(a_bzrdir)
2089
1862
        if (isinstance(a_bzrdir, RemoteBzrDir) and
2090
1863
            not isinstance(result, RemoteBranch)):
2091
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2092
 
                                  name=name)
 
1864
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2093
1865
        return result
2094
1866
 
2095
 
    def initialize(self, a_bzrdir, name=None):
 
1867
    def initialize(self, a_bzrdir):
2096
1868
        # 1) get the network name to use.
2097
1869
        if self._custom_format:
2098
1870
            network_name = self._custom_format.network_name()
2104
1876
            network_name = reference_format.network_name()
2105
1877
        # Being asked to create on a non RemoteBzrDir:
2106
1878
        if not isinstance(a_bzrdir, RemoteBzrDir):
2107
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1879
            return self._vfs_initialize(a_bzrdir)
2108
1880
        medium = a_bzrdir._client._medium
2109
1881
        if medium._is_remote_before((1, 13)):
2110
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1882
            return self._vfs_initialize(a_bzrdir)
2111
1883
        # Creating on a remote bzr dir.
2112
1884
        # 2) try direct creation via RPC
2113
1885
        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)
2117
1886
        verb = 'BzrDir.create_branch'
2118
1887
        try:
2119
1888
            response = a_bzrdir._call(verb, path, network_name)
2120
1889
        except errors.UnknownSmartMethod:
2121
1890
            # Fallback - use vfs methods
2122
1891
            medium._remember_remote_is_before((1, 13))
2123
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1892
            return self._vfs_initialize(a_bzrdir)
2124
1893
        if response[0] != 'ok':
2125
1894
            raise errors.UnexpectedSmartServerResponse(response)
2126
1895
        # Turn the response into a RemoteRepository object.
2134
1903
                a_bzrdir._client)
2135
1904
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2136
1905
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2137
 
            format=format, setup_stacking=False, name=name)
 
1906
            format=format, setup_stacking=False)
2138
1907
        # XXX: We know this is a new branch, so it must have revno 0, revid
2139
1908
        # NULL_REVISION. Creating the branch locked would make this be unable
2140
1909
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2160
1929
        return self._custom_format.supports_set_append_revisions_only()
2161
1930
 
2162
1931
 
2163
 
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
 
1932
class RemoteBranch(branch.Branch, _RpcHelper):
2164
1933
    """Branch stored on a server accessed by HPSS RPC.
2165
1934
 
2166
1935
    At the moment most operations are mapped down to simple file operations.
2167
1936
    """
2168
1937
 
2169
1938
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2170
 
        _client=None, format=None, setup_stacking=True, name=None):
 
1939
        _client=None, format=None, setup_stacking=True):
2171
1940
        """Create a RemoteBranch instance.
2172
1941
 
2173
1942
        :param real_branch: An optional local implementation of the branch
2179
1948
        :param setup_stacking: If True make an RPC call to determine the
2180
1949
            stacked (or not) status of the branch. If False assume the branch
2181
1950
            is not stacked.
2182
 
        :param name: Colocated branch name
2183
1951
        """
2184
1952
        # We intentionally don't call the parent class's __init__, because it
2185
1953
        # will try to assign to self.tags, which is a property in this subclass.
2204
1972
            self._real_branch = None
2205
1973
        # Fill out expected attributes of branch for bzrlib API users.
2206
1974
        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
 
1975
        self.base = self.bzrdir.root_transport.base
2210
1976
        self._control_files = None
2211
1977
        self._lock_mode = None
2212
1978
        self._lock_token = None
2223
1989
                    self._real_branch._format.network_name()
2224
1990
        else:
2225
1991
            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
2229
1992
        if not self._format._network_name:
2230
1993
            # Did not get from open_branchV2 - old server.
2231
1994
            self._ensure_real()
2276
2039
                raise AssertionError('smart server vfs must be enabled '
2277
2040
                    'to use vfs implementation')
2278
2041
            self.bzrdir._ensure_real()
2279
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2280
 
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
2042
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2281
2043
            if self.repository._real_repository is None:
2282
2044
                # Give the remote repository the matching real repo.
2283
2045
                real_repo = self._real_branch.repository
2379
2141
            return self._vfs_get_tags_bytes()
2380
2142
        return response[0]
2381
2143
 
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
 
 
2400
2144
    def lock_read(self):
2401
 
        """Lock the branch for read operations.
2402
 
 
2403
 
        :return: A bzrlib.lock.LogicalLockResult.
2404
 
        """
2405
2145
        self.repository.lock_read()
2406
2146
        if not self._lock_mode:
2407
 
            self._note_lock('r')
2408
2147
            self._lock_mode = 'r'
2409
2148
            self._lock_count = 1
2410
2149
            if self._real_branch is not None:
2411
2150
                self._real_branch.lock_read()
2412
2151
        else:
2413
2152
            self._lock_count += 1
2414
 
        return lock.LogicalLockResult(self.unlock)
2415
2153
 
2416
2154
    def _remote_lock_write(self, token):
2417
2155
        if token is None:
2418
2156
            branch_token = repo_token = ''
2419
2157
        else:
2420
2158
            branch_token = token
2421
 
            repo_token = self.repository.lock_write().repository_token
 
2159
            repo_token = self.repository.lock_write()
2422
2160
            self.repository.unlock()
2423
2161
        err_context = {'token': token}
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])
 
2162
        response = self._call(
 
2163
            'Branch.lock_write', self._remote_path(), branch_token,
 
2164
            repo_token or '', **err_context)
2434
2165
        if response[0] != 'ok':
2435
2166
            raise errors.UnexpectedSmartServerResponse(response)
2436
2167
        ok, branch_token, repo_token = response
2438
2169
 
2439
2170
    def lock_write(self, token=None):
2440
2171
        if not self._lock_mode:
2441
 
            self._note_lock('w')
2442
2172
            # Lock the branch and repo in one remote call.
2443
2173
            remote_tokens = self._remote_lock_write(token)
2444
2174
            self._lock_token, self._repo_lock_token = remote_tokens
2457
2187
            self._lock_mode = 'w'
2458
2188
            self._lock_count = 1
2459
2189
        elif self._lock_mode == 'r':
2460
 
            raise errors.ReadOnlyError(self)
 
2190
            raise errors.ReadOnlyTransaction
2461
2191
        else:
2462
2192
            if token is not None:
2463
2193
                # A token was given to lock_write, and we're relocking, so
2468
2198
            self._lock_count += 1
2469
2199
            # Re-lock the repository too.
2470
2200
            self.repository.lock_write(self._repo_lock_token)
2471
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
2201
        return self._lock_token or None
 
2202
 
 
2203
    def _set_tags_bytes(self, bytes):
 
2204
        self._ensure_real()
 
2205
        return self._real_branch._set_tags_bytes(bytes)
2472
2206
 
2473
2207
    def _unlock(self, branch_token, repo_token):
2474
2208
        err_context = {'token': str((branch_token, repo_token))}
2479
2213
            return
2480
2214
        raise errors.UnexpectedSmartServerResponse(response)
2481
2215
 
2482
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2483
2216
    def unlock(self):
2484
2217
        try:
2485
2218
            self._lock_count -= 1
2525
2258
            raise NotImplementedError(self.dont_leave_lock_in_place)
2526
2259
        self._leave_lock = False
2527
2260
 
2528
 
    @needs_read_lock
2529
2261
    def get_rev_id(self, revno, history=None):
2530
2262
        if revno == 0:
2531
2263
            return _mod_revision.NULL_REVISION
2797
2529
        medium = self._branch._client._medium
2798
2530
        if medium._is_remote_before((1, 14)):
2799
2531
            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
2532
        try:
2809
2533
            path = self._branch._remote_path()
2810
2534
            response = self._branch._client.call('Branch.set_config_option',
2811
2535
                path, self._branch._lock_token, self._branch._repo_lock_token,
2812
2536
                value.encode('utf8'), name, section or '')
2813
2537
        except errors.UnknownSmartMethod:
2814
 
            medium = self._branch._client._medium
2815
2538
            medium._remember_remote_is_before((1, 14))
2816
2539
            return self._vfs_set_option(value, name, section)
2817
2540
        if response != ():
2818
2541
            raise errors.UnexpectedSmartServerResponse(response)
2819
2542
 
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
2543
    def _real_object(self):
2846
2544
        self._branch._ensure_real()
2847
2545
        return self._branch._real_branch
2930
2628
                    'Missing key %r in context %r', key_err.args[0], context)
2931
2629
                raise err
2932
2630
 
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':
 
2631
    if err.error_verb == 'NoSuchRevision':
2937
2632
        raise NoSuchRevision(find('branch'), err.error_args[0])
2938
2633
    elif err.error_verb == 'nosuchrevision':
2939
2634
        raise NoSuchRevision(find('repository'), err.error_args[0])
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)
 
2635
    elif err.error_tuple == ('nobranch',):
 
2636
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2947
2637
    elif err.error_verb == 'norepository':
2948
2638
        raise errors.NoRepositoryPresent(find('bzrdir'))
2949
2639
    elif err.error_verb == 'LockContention':