~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

(parthm) Better regex compile errors (Parth Malwankar)

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 (
27
24
    debug,
28
25
    errors,
29
26
    graph,
 
27
    lock,
30
28
    lockdir,
31
29
    repository,
 
30
    repository as _mod_repository,
32
31
    revision,
33
32
    revision as _mod_revision,
 
33
    static_tuple,
34
34
    symbol_versioning,
35
35
)
36
 
from bzrlib.branch import BranchReferenceFormat
 
36
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
37
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
39
from bzrlib.errors import (
40
40
    NoSuchRevision,
41
41
    SmartProtocolError,
43
43
from bzrlib.lockable_files import LockableFiles
44
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
45
from bzrlib.revision import ensure_null, NULL_REVISION
 
46
from bzrlib.repository import RepositoryWriteLockResult
46
47
from bzrlib.trace import mutter, note, warning
47
48
 
48
49
 
61
62
        except errors.ErrorFromSmartServer, err:
62
63
            self._translate_error(err, **err_context)
63
64
 
 
65
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
 
66
        try:
 
67
            return self._client.call_with_body_bytes(method, args, body_bytes)
 
68
        except errors.ErrorFromSmartServer, err:
 
69
            self._translate_error(err, **err_context)
 
70
 
64
71
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
65
72
                                             **err_context):
66
73
        try:
85
92
class RemoteBzrDir(BzrDir, _RpcHelper):
86
93
    """Control directory on a remote server, accessed via bzr:// or similar."""
87
94
 
88
 
    def __init__(self, transport, format, _client=None):
 
95
    def __init__(self, transport, format, _client=None, _force_probe=False):
89
96
        """Construct a RemoteBzrDir.
90
97
 
91
98
        :param _client: Private parameter for testing. Disables probing and the
95
102
        # this object holds a delegated bzrdir that uses file-level operations
96
103
        # to talk to the other side
97
104
        self._real_bzrdir = None
 
105
        self._has_working_tree = None
98
106
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
107
        # create_branch for details.
100
108
        self._next_open_branch_result = None
104
112
            self._client = client._SmartClient(medium)
105
113
        else:
106
114
            self._client = _client
107
 
            return
108
 
 
 
115
            if not _force_probe:
 
116
                return
 
117
 
 
118
        self._probe_bzrdir()
 
119
 
 
120
    def __repr__(self):
 
121
        return '%s(%r)' % (self.__class__.__name__, self._client)
 
122
 
 
123
    def _probe_bzrdir(self):
 
124
        medium = self._client._medium
109
125
        path = self._path_for_remote_call(self._client)
 
126
        if medium._is_remote_before((2, 1)):
 
127
            self._rpc_open(path)
 
128
            return
 
129
        try:
 
130
            self._rpc_open_2_1(path)
 
131
            return
 
132
        except errors.UnknownSmartMethod:
 
133
            medium._remember_remote_is_before((2, 1))
 
134
            self._rpc_open(path)
 
135
 
 
136
    def _rpc_open_2_1(self, path):
 
137
        response = self._call('BzrDir.open_2.1', path)
 
138
        if response == ('no',):
 
139
            raise errors.NotBranchError(path=self.root_transport.base)
 
140
        elif response[0] == 'yes':
 
141
            if response[1] == 'yes':
 
142
                self._has_working_tree = True
 
143
            elif response[1] == 'no':
 
144
                self._has_working_tree = False
 
145
            else:
 
146
                raise errors.UnexpectedSmartServerResponse(response)
 
147
        else:
 
148
            raise errors.UnexpectedSmartServerResponse(response)
 
149
 
 
150
    def _rpc_open(self, path):
110
151
        response = self._call('BzrDir.open', path)
111
152
        if response not in [('yes',), ('no',)]:
112
153
            raise errors.UnexpectedSmartServerResponse(response)
113
154
        if response == ('no',):
114
 
            raise errors.NotBranchError(path=transport.base)
 
155
            raise errors.NotBranchError(path=self.root_transport.base)
115
156
 
116
157
    def _ensure_real(self):
117
158
        """Ensure that there is a _real_bzrdir set.
119
160
        Used before calls to self._real_bzrdir.
120
161
        """
121
162
        if not self._real_bzrdir:
 
163
            if 'hpssvfs' in debug.debug_flags:
 
164
                import traceback
 
165
                warning('VFS BzrDir access triggered\n%s',
 
166
                    ''.join(traceback.format_stack()))
122
167
            self._real_bzrdir = BzrDir.open_from_transport(
123
168
                self.root_transport, _server_formats=False)
124
169
            self._format._network_name = \
200
245
        self._ensure_real()
201
246
        self._real_bzrdir.destroy_repository()
202
247
 
203
 
    def create_branch(self):
 
248
    def create_branch(self, name=None):
204
249
        # as per meta1 formats - just delegate to the format object which may
205
250
        # be parameterised.
206
 
        real_branch = self._format.get_branch_format().initialize(self)
 
251
        real_branch = self._format.get_branch_format().initialize(self,
 
252
            name=name)
207
253
        if not isinstance(real_branch, RemoteBranch):
208
 
            result = RemoteBranch(self, self.find_repository(), real_branch)
 
254
            result = RemoteBranch(self, self.find_repository(), real_branch,
 
255
                                  name=name)
209
256
        else:
210
257
            result = real_branch
211
258
        # BzrDir.clone_on_transport() uses the result of create_branch but does
217
264
        self._next_open_branch_result = result
218
265
        return result
219
266
 
220
 
    def destroy_branch(self):
 
267
    def destroy_branch(self, name=None):
221
268
        """See BzrDir.destroy_branch"""
222
269
        self._ensure_real()
223
 
        self._real_bzrdir.destroy_branch()
 
270
        self._real_bzrdir.destroy_branch(name=name)
224
271
        self._next_open_branch_result = None
225
272
 
226
273
    def create_workingtree(self, revision_id=None, from_branch=None):
227
274
        raise errors.NotLocalUrl(self.transport.base)
228
275
 
229
 
    def find_branch_format(self):
 
276
    def find_branch_format(self, name=None):
230
277
        """Find the branch 'format' for this bzrdir.
231
278
 
232
279
        This might be a synthetic object for e.g. RemoteBranch and SVN.
233
280
        """
234
 
        b = self.open_branch()
 
281
        b = self.open_branch(name=name)
235
282
        return b._format
236
283
 
237
 
    def get_branch_reference(self):
 
284
    def get_branch_reference(self, name=None):
238
285
        """See BzrDir.get_branch_reference()."""
 
286
        if name is not None:
 
287
            # XXX JRV20100304: Support opening colocated branches
 
288
            raise errors.NoColocatedBranchSupport(self)
239
289
        response = self._get_branch_reference()
240
290
        if response[0] == 'ref':
241
291
            return response[1]
245
295
    def _get_branch_reference(self):
246
296
        path = self._path_for_remote_call(self._client)
247
297
        medium = self._client._medium
248
 
        if not medium._is_remote_before((1, 13)):
 
298
        candidate_calls = [
 
299
            ('BzrDir.open_branchV3', (2, 1)),
 
300
            ('BzrDir.open_branchV2', (1, 13)),
 
301
            ('BzrDir.open_branch', None),
 
302
            ]
 
303
        for verb, required_version in candidate_calls:
 
304
            if required_version and medium._is_remote_before(required_version):
 
305
                continue
249
306
            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
 
307
                response = self._call(verb, path)
254
308
            except errors.UnknownSmartMethod:
255
 
                medium._remember_remote_is_before((1, 13))
256
 
        response = self._call('BzrDir.open_branch', path)
257
 
        if response[0] != 'ok':
 
309
                if required_version is None:
 
310
                    raise
 
311
                medium._remember_remote_is_before(required_version)
 
312
            else:
 
313
                break
 
314
        if verb == 'BzrDir.open_branch':
 
315
            if response[0] != 'ok':
 
316
                raise errors.UnexpectedSmartServerResponse(response)
 
317
            if response[1] != '':
 
318
                return ('ref', response[1])
 
319
            else:
 
320
                return ('branch', '')
 
321
        if response[0] not in ('ref', 'branch'):
258
322
            raise errors.UnexpectedSmartServerResponse(response)
259
 
        if response[1] != '':
260
 
            return ('ref', response[1])
261
 
        else:
262
 
            return ('branch', '')
 
323
        return response
263
324
 
264
 
    def _get_tree_branch(self):
 
325
    def _get_tree_branch(self, name=None):
265
326
        """See BzrDir._get_tree_branch()."""
266
 
        return None, self.open_branch()
 
327
        return None, self.open_branch(name=name)
267
328
 
268
 
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
269
 
        if _unsupported:
 
329
    def open_branch(self, name=None, unsupported=False,
 
330
                    ignore_fallbacks=False):
 
331
        if unsupported:
270
332
            raise NotImplementedError('unsupported flag support not implemented yet.')
271
333
        if self._next_open_branch_result is not None:
272
334
            # See create_branch for details.
277
339
        if response[0] == 'ref':
278
340
            # a branch reference, use the existing BranchReference logic.
279
341
            format = BranchReferenceFormat()
280
 
            return format.open(self, _found=True, location=response[1],
281
 
                ignore_fallbacks=ignore_fallbacks)
 
342
            return format.open(self, name=name, _found=True,
 
343
                location=response[1], ignore_fallbacks=ignore_fallbacks)
282
344
        branch_format_name = response[1]
283
345
        if not branch_format_name:
284
346
            branch_format_name = None
285
347
        format = RemoteBranchFormat(network_name=branch_format_name)
286
348
        return RemoteBranch(self, self.find_repository(), format=format,
287
 
            setup_stacking=not ignore_fallbacks)
 
349
            setup_stacking=not ignore_fallbacks, name=name)
288
350
 
289
351
    def _open_repo_v1(self, path):
290
352
        verb = 'BzrDir.find_repository'
351
413
        else:
352
414
            raise errors.NoRepositoryPresent(self)
353
415
 
 
416
    def has_workingtree(self):
 
417
        if self._has_working_tree is None:
 
418
            self._ensure_real()
 
419
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
420
        return self._has_working_tree
 
421
 
354
422
    def open_workingtree(self, recommend_upgrade=True):
355
 
        self._ensure_real()
356
 
        if self._real_bzrdir.has_workingtree():
 
423
        if self.has_workingtree():
357
424
            raise errors.NotLocalUrl(self.root_transport)
358
425
        else:
359
426
            raise errors.NoWorkingTree(self.root_transport.base)
362
429
        """Return the path to be used for this bzrdir in a remote call."""
363
430
        return client.remote_path_from_transport(self.root_transport)
364
431
 
365
 
    def get_branch_transport(self, branch_format):
 
432
    def get_branch_transport(self, branch_format, name=None):
366
433
        self._ensure_real()
367
 
        return self._real_bzrdir.get_branch_transport(branch_format)
 
434
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
368
435
 
369
436
    def get_repository_transport(self, repository_format):
370
437
        self._ensure_real()
422
489
        self._custom_format = None
423
490
        self._network_name = None
424
491
        self._creating_bzrdir = None
 
492
        self._supports_chks = None
425
493
        self._supports_external_lookups = None
426
494
        self._supports_tree_reference = None
427
495
        self._rich_root_data = None
428
496
 
 
497
    def __repr__(self):
 
498
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
499
            self._network_name)
 
500
 
429
501
    @property
430
502
    def fast_deltas(self):
431
503
        self._ensure_real()
439
511
        return self._rich_root_data
440
512
 
441
513
    @property
 
514
    def supports_chks(self):
 
515
        if self._supports_chks is None:
 
516
            self._ensure_real()
 
517
            self._supports_chks = self._custom_format.supports_chks
 
518
        return self._supports_chks
 
519
 
 
520
    @property
442
521
    def supports_external_lookups(self):
443
522
        if self._supports_external_lookups is None:
444
523
            self._ensure_real()
545
624
        return self._custom_format._fetch_reconcile
546
625
 
547
626
    def get_format_description(self):
548
 
        return 'bzr remote repository'
 
627
        self._ensure_real()
 
628
        return 'Remote: ' + self._custom_format.get_format_description()
549
629
 
550
630
    def __eq__(self, other):
551
631
        return self.__class__ is other.__class__
552
632
 
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
 
 
562
633
    def network_name(self):
563
634
        if self._network_name:
564
635
            return self._network_name
576
647
        return self._custom_format._serializer
577
648
 
578
649
 
579
 
class RemoteRepository(_RpcHelper):
 
650
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
 
651
    bzrdir.ControlComponent):
580
652
    """Repository accessed over rpc.
581
653
 
582
654
    For the moment most operations are performed using local transport-backed
625
697
        # Additional places to query for data.
626
698
        self._fallback_repositories = []
627
699
 
 
700
    @property
 
701
    def user_transport(self):
 
702
        return self.bzrdir.user_transport
 
703
 
 
704
    @property
 
705
    def control_transport(self):
 
706
        # XXX: Normally you shouldn't directly get at the remote repository
 
707
        # transport, but I'm not sure it's worth making this method
 
708
        # optional -- mbp 2010-04-21
 
709
        return self.bzrdir.get_repository_transport(None)
 
710
        
628
711
    def __str__(self):
629
712
        return "%s(%s)" % (self.__class__.__name__, self.base)
630
713
 
813
896
            result.add(_mod_revision.NULL_REVISION)
814
897
        return result
815
898
 
 
899
    def _has_same_fallbacks(self, other_repo):
 
900
        """Returns true if the repositories have the same fallbacks."""
 
901
        # XXX: copied from Repository; it should be unified into a base class
 
902
        # <https://bugs.launchpad.net/bzr/+bug/401622>
 
903
        my_fb = self._fallback_repositories
 
904
        other_fb = other_repo._fallback_repositories
 
905
        if len(my_fb) != len(other_fb):
 
906
            return False
 
907
        for f, g in zip(my_fb, other_fb):
 
908
            if not f.has_same_location(g):
 
909
                return False
 
910
        return True
 
911
 
816
912
    def has_same_location(self, other):
 
913
        # TODO: Move to RepositoryBase and unify with the regular Repository
 
914
        # one; unfortunately the tests rely on slightly different behaviour at
 
915
        # present -- mbp 20090710
817
916
        return (self.__class__ is other.__class__ and
818
917
                self.bzrdir.transport.base == other.bzrdir.transport.base)
819
918
 
822
921
        parents_provider = self._make_parents_provider(other_repository)
823
922
        return graph.Graph(parents_provider)
824
923
 
 
924
    @needs_read_lock
 
925
    def get_known_graph_ancestry(self, revision_ids):
 
926
        """Return the known graph for a set of revision ids and their ancestors.
 
927
        """
 
928
        st = static_tuple.StaticTuple
 
929
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
930
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
931
        return graph.GraphThunkIdsToKeys(known_graph)
 
932
 
825
933
    def gather_stats(self, revid=None, committers=None):
826
934
        """See Repository.gather_stats()."""
827
935
        path = self.bzrdir._path_for_remote_call(self._client)
887
995
    def is_write_locked(self):
888
996
        return self._lock_mode == 'w'
889
997
 
 
998
    def _warn_if_deprecated(self, branch=None):
 
999
        # If we have a real repository, the check will be done there, if we
 
1000
        # don't the check will be done remotely.
 
1001
        pass
 
1002
 
890
1003
    def lock_read(self):
 
1004
        """Lock the repository for read operations.
 
1005
 
 
1006
        :return: A bzrlib.lock.LogicalLockResult.
 
1007
        """
891
1008
        # wrong eventually - want a local lock cache context
892
1009
        if not self._lock_mode:
 
1010
            self._note_lock('r')
893
1011
            self._lock_mode = 'r'
894
1012
            self._lock_count = 1
895
1013
            self._unstacked_provider.enable_cache(cache_misses=True)
899
1017
                repo.lock_read()
900
1018
        else:
901
1019
            self._lock_count += 1
 
1020
        return lock.LogicalLockResult(self.unlock)
902
1021
 
903
1022
    def _remote_lock_write(self, token):
904
1023
        path = self.bzrdir._path_for_remote_call(self._client)
915
1034
 
916
1035
    def lock_write(self, token=None, _skip_rpc=False):
917
1036
        if not self._lock_mode:
 
1037
            self._note_lock('w')
918
1038
            if _skip_rpc:
919
1039
                if self._lock_token is not None:
920
1040
                    if token != self._lock_token:
943
1063
            raise errors.ReadOnlyError(self)
944
1064
        else:
945
1065
            self._lock_count += 1
946
 
        return self._lock_token or None
 
1066
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
947
1067
 
948
1068
    def leave_lock_in_place(self):
949
1069
        if not self._lock_token:
1023
1143
        else:
1024
1144
            raise errors.UnexpectedSmartServerResponse(response)
1025
1145
 
 
1146
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1026
1147
    def unlock(self):
1027
1148
        if not self._lock_count:
1028
 
            raise errors.LockNotHeld(self)
 
1149
            return lock.cant_unlock_not_held(self)
1029
1150
        self._lock_count -= 1
1030
1151
        if self._lock_count > 0:
1031
1152
            return
1128
1249
            # state, so always add a lock here. If a caller passes us a locked
1129
1250
            # repository, they are responsible for unlocking it later.
1130
1251
            repository.lock_read()
 
1252
        self._check_fallback_repository(repository)
1131
1253
        self._fallback_repositories.append(repository)
1132
1254
        # If self._real_repository was parameterised already (e.g. because a
1133
1255
        # _real_branch had its get_stacked_on_url method called), then the
1134
1256
        # repository to be added may already be in the _real_repositories list.
1135
1257
        if self._real_repository is not None:
1136
 
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
 
1258
            fallback_locations = [repo.user_url for repo in
1137
1259
                self._real_repository._fallback_repositories]
1138
 
            if repository.bzrdir.root_transport.base not in fallback_locations:
 
1260
            if repository.user_url not in fallback_locations:
1139
1261
                self._real_repository.add_fallback_repository(repository)
1140
1262
 
 
1263
    def _check_fallback_repository(self, repository):
 
1264
        """Check that this repository can fallback to repository safely.
 
1265
 
 
1266
        Raise an error if not.
 
1267
 
 
1268
        :param repository: A repository to fallback to.
 
1269
        """
 
1270
        return _mod_repository.InterRepository._assert_same_model(
 
1271
            self, repository)
 
1272
 
1141
1273
    def add_inventory(self, revid, inv, parents):
1142
1274
        self._ensure_real()
1143
1275
        return self._real_repository.add_inventory(revid, inv, parents)
1144
1276
 
1145
1277
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1146
 
                               parents):
 
1278
            parents, basis_inv=None, propagate_caches=False):
1147
1279
        self._ensure_real()
1148
1280
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1149
 
            delta, new_revision_id, parents)
 
1281
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1282
            propagate_caches=propagate_caches)
1150
1283
 
1151
1284
    def add_revision(self, rev_id, rev, inv=None, config=None):
1152
1285
        self._ensure_real()
1158
1291
        self._ensure_real()
1159
1292
        return self._real_repository.get_inventory(revision_id)
1160
1293
 
1161
 
    def iter_inventories(self, revision_ids):
 
1294
    def iter_inventories(self, revision_ids, ordering=None):
1162
1295
        self._ensure_real()
1163
 
        return self._real_repository.iter_inventories(revision_ids)
 
1296
        return self._real_repository.iter_inventories(revision_ids, ordering)
1164
1297
 
1165
1298
    @needs_read_lock
1166
1299
    def get_revision(self, revision_id):
1182
1315
        return self._real_repository.make_working_trees()
1183
1316
 
1184
1317
    def refresh_data(self):
1185
 
        """Re-read any data needed to to synchronise with disk.
 
1318
        """Re-read any data needed to synchronise with disk.
1186
1319
 
1187
1320
        This method is intended to be called after another repository instance
1188
1321
        (such as one used by a smart server) has inserted data into the
1189
 
        repository. It may not be called during a write group, but may be
1190
 
        called at any other time.
 
1322
        repository. On all repositories this will work outside of write groups.
 
1323
        Some repository formats (pack and newer for bzrlib native formats)
 
1324
        support refresh_data inside write groups. If called inside a write
 
1325
        group on a repository that does not support refreshing in a write group
 
1326
        IsInWriteGroupError will be raised.
1191
1327
        """
1192
 
        if self.is_in_write_group():
1193
 
            raise errors.InternalBzrError(
1194
 
                "May not refresh_data while in a write group.")
1195
1328
        if self._real_repository is not None:
1196
1329
            self._real_repository.refresh_data()
1197
1330
 
1230
1363
            raise errors.InternalBzrError(
1231
1364
                "May not fetch while in a write group.")
1232
1365
        # fast path same-url fetch operations
1233
 
        if self.has_same_location(source) and fetch_spec is None:
 
1366
        if (self.has_same_location(source)
 
1367
            and fetch_spec is None
 
1368
            and self._has_same_fallbacks(source)):
1234
1369
            # check that last_revision is in 'from' and then return a
1235
1370
            # no-operation.
1236
1371
            if (revision_id is not None and
1409
1544
        return self._real_repository.get_signature_text(revision_id)
1410
1545
 
1411
1546
    @needs_read_lock
1412
 
    def get_inventory_xml(self, revision_id):
1413
 
        self._ensure_real()
1414
 
        return self._real_repository.get_inventory_xml(revision_id)
1415
 
 
1416
 
    def deserialise_inventory(self, revision_id, xml):
1417
 
        self._ensure_real()
1418
 
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
1547
    def _get_inventory_xml(self, revision_id):
 
1548
        self._ensure_real()
 
1549
        return self._real_repository._get_inventory_xml(revision_id)
1419
1550
 
1420
1551
    def reconcile(self, other=None, thorough=False):
1421
1552
        self._ensure_real()
1448
1579
        return self._real_repository.get_revision_reconcile(revision_id)
1449
1580
 
1450
1581
    @needs_read_lock
1451
 
    def check(self, revision_ids=None):
 
1582
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1452
1583
        self._ensure_real()
1453
 
        return self._real_repository.check(revision_ids=revision_ids)
 
1584
        return self._real_repository.check(revision_ids=revision_ids,
 
1585
            callback_refs=callback_refs, check_repo=check_repo)
1454
1586
 
1455
1587
    def copy_content_into(self, destination, revision_id=None):
1456
1588
        self._ensure_real()
1496
1628
        return self._real_repository.inventories
1497
1629
 
1498
1630
    @needs_write_lock
1499
 
    def pack(self, hint=None):
 
1631
    def pack(self, hint=None, clean_obsolete_packs=False):
1500
1632
        """Compress the data within the repository.
1501
1633
 
1502
1634
        This is not currently implemented within the smart server.
1503
1635
        """
1504
1636
        self._ensure_real()
1505
 
        return self._real_repository.pack(hint=hint)
 
1637
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1506
1638
 
1507
1639
    @property
1508
1640
    def revisions(self):
1596
1728
        self._ensure_real()
1597
1729
        return self._real_repository.revision_graph_can_have_wrong_parents()
1598
1730
 
1599
 
    def _find_inconsistent_revision_parents(self):
 
1731
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1600
1732
        self._ensure_real()
1601
 
        return self._real_repository._find_inconsistent_revision_parents()
 
1733
        return self._real_repository._find_inconsistent_revision_parents(
 
1734
            revisions_iterator)
1602
1735
 
1603
1736
    def _check_for_inconsistent_revision_parents(self):
1604
1737
        self._ensure_real()
1658
1791
    def insert_stream(self, stream, src_format, resume_tokens):
1659
1792
        target = self.target_repo
1660
1793
        target._unstacked_provider.missing_keys.clear()
 
1794
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1661
1795
        if target._lock_token:
1662
 
            verb = 'Repository.insert_stream_locked'
1663
 
            extra_args = (target._lock_token or '',)
1664
 
            required_version = (1, 14)
 
1796
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1797
            lock_args = (target._lock_token or '',)
1665
1798
        else:
1666
 
            verb = 'Repository.insert_stream'
1667
 
            extra_args = ()
1668
 
            required_version = (1, 13)
 
1799
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
1800
            lock_args = ()
1669
1801
        client = target._client
1670
1802
        medium = client._medium
1671
 
        if medium._is_remote_before(required_version):
1672
 
            # No possible way this can work.
1673
 
            return self._insert_real(stream, src_format, resume_tokens)
1674
1803
        path = target.bzrdir._path_for_remote_call(client)
1675
 
        if not resume_tokens:
1676
 
            # XXX: Ugly but important for correctness, *will* be fixed during
1677
 
            # 1.13 cycle. Pushing a stream that is interrupted results in a
1678
 
            # fallback to the _real_repositories sink *with a partial stream*.
1679
 
            # Thats bad because we insert less data than bzr expected. To avoid
1680
 
            # this we do a trial push to make sure the verb is accessible, and
1681
 
            # do not fallback when actually pushing the stream. A cleanup patch
1682
 
            # is going to look at rewinding/restarting the stream/partial
1683
 
            # buffering etc.
 
1804
        # Probe for the verb to use with an empty stream before sending the
 
1805
        # real stream to it.  We do this both to avoid the risk of sending a
 
1806
        # large request that is then rejected, and because we don't want to
 
1807
        # implement a way to buffer, rewind, or restart the stream.
 
1808
        found_verb = False
 
1809
        for verb, required_version in candidate_calls:
 
1810
            if medium._is_remote_before(required_version):
 
1811
                continue
 
1812
            if resume_tokens:
 
1813
                # We've already done the probing (and set _is_remote_before) on
 
1814
                # a previous insert.
 
1815
                found_verb = True
 
1816
                break
1684
1817
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1685
1818
            try:
1686
1819
                response = client.call_with_body_stream(
1687
 
                    (verb, path, '') + extra_args, byte_stream)
 
1820
                    (verb, path, '') + lock_args, byte_stream)
1688
1821
            except errors.UnknownSmartMethod:
1689
1822
                medium._remember_remote_is_before(required_version)
1690
 
                return self._insert_real(stream, src_format, resume_tokens)
 
1823
            else:
 
1824
                found_verb = True
 
1825
                break
 
1826
        if not found_verb:
 
1827
            # Have to use VFS.
 
1828
            return self._insert_real(stream, src_format, resume_tokens)
 
1829
        self._last_inv_record = None
 
1830
        self._last_substream = None
 
1831
        if required_version < (1, 19):
 
1832
            # Remote side doesn't support inventory deltas.  Wrap the stream to
 
1833
            # make sure we don't send any.  If the stream contains inventory
 
1834
            # deltas we'll interrupt the smart insert_stream request and
 
1835
            # fallback to VFS.
 
1836
            stream = self._stop_stream_if_inventory_delta(stream)
1691
1837
        byte_stream = smart_repo._stream_to_byte_stream(
1692
1838
            stream, src_format)
1693
1839
        resume_tokens = ' '.join(resume_tokens)
1694
1840
        response = client.call_with_body_stream(
1695
 
            (verb, path, resume_tokens) + extra_args, byte_stream)
 
1841
            (verb, path, resume_tokens) + lock_args, byte_stream)
1696
1842
        if response[0][0] not in ('ok', 'missing-basis'):
1697
1843
            raise errors.UnexpectedSmartServerResponse(response)
 
1844
        if self._last_substream is not None:
 
1845
            # The stream included an inventory-delta record, but the remote
 
1846
            # side isn't new enough to support them.  So we need to send the
 
1847
            # rest of the stream via VFS.
 
1848
            self.target_repo.refresh_data()
 
1849
            return self._resume_stream_with_vfs(response, src_format)
1698
1850
        if response[0][0] == 'missing-basis':
1699
1851
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1700
1852
            resume_tokens = tokens
1703
1855
            self.target_repo.refresh_data()
1704
1856
            return [], set()
1705
1857
 
 
1858
    def _resume_stream_with_vfs(self, response, src_format):
 
1859
        """Resume sending a stream via VFS, first resending the record and
 
1860
        substream that couldn't be sent via an insert_stream verb.
 
1861
        """
 
1862
        if response[0][0] == 'missing-basis':
 
1863
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1864
            # Ignore missing_keys, we haven't finished inserting yet
 
1865
        else:
 
1866
            tokens = []
 
1867
        def resume_substream():
 
1868
            # Yield the substream that was interrupted.
 
1869
            for record in self._last_substream:
 
1870
                yield record
 
1871
            self._last_substream = None
 
1872
        def resume_stream():
 
1873
            # Finish sending the interrupted substream
 
1874
            yield ('inventory-deltas', resume_substream())
 
1875
            # Then simply continue sending the rest of the stream.
 
1876
            for substream_kind, substream in self._last_stream:
 
1877
                yield substream_kind, substream
 
1878
        return self._insert_real(resume_stream(), src_format, tokens)
 
1879
 
 
1880
    def _stop_stream_if_inventory_delta(self, stream):
 
1881
        """Normally this just lets the original stream pass-through unchanged.
 
1882
 
 
1883
        However if any 'inventory-deltas' substream occurs it will stop
 
1884
        streaming, and store the interrupted substream and stream in
 
1885
        self._last_substream and self._last_stream so that the stream can be
 
1886
        resumed by _resume_stream_with_vfs.
 
1887
        """
 
1888
                    
 
1889
        stream_iter = iter(stream)
 
1890
        for substream_kind, substream in stream_iter:
 
1891
            if substream_kind == 'inventory-deltas':
 
1892
                self._last_substream = substream
 
1893
                self._last_stream = stream_iter
 
1894
                return
 
1895
            else:
 
1896
                yield substream_kind, substream
 
1897
            
1706
1898
 
1707
1899
class RemoteStreamSource(repository.StreamSource):
1708
1900
    """Stream data from a remote server."""
1711
1903
        if (self.from_repository._fallback_repositories and
1712
1904
            self.to_format._fetch_order == 'topological'):
1713
1905
            return self._real_stream(self.from_repository, search)
1714
 
        return self.missing_parents_chain(search, [self.from_repository] +
1715
 
            self.from_repository._fallback_repositories)
 
1906
        sources = []
 
1907
        seen = set()
 
1908
        repos = [self.from_repository]
 
1909
        while repos:
 
1910
            repo = repos.pop(0)
 
1911
            if repo in seen:
 
1912
                continue
 
1913
            seen.add(repo)
 
1914
            repos.extend(repo._fallback_repositories)
 
1915
            sources.append(repo)
 
1916
        return self.missing_parents_chain(search, sources)
 
1917
 
 
1918
    def get_stream_for_missing_keys(self, missing_keys):
 
1919
        self.from_repository._ensure_real()
 
1920
        real_repo = self.from_repository._real_repository
 
1921
        real_source = real_repo._get_source(self.to_format)
 
1922
        return real_source.get_stream_for_missing_keys(missing_keys)
1716
1923
 
1717
1924
    def _real_stream(self, repo, search):
1718
1925
        """Get a stream for search from repo.
1725
1932
        """
1726
1933
        source = repo._get_source(self.to_format)
1727
1934
        if isinstance(source, RemoteStreamSource):
1728
 
            return repository.StreamSource.get_stream(source, search)
 
1935
            repo._ensure_real()
 
1936
            source = repo._real_repository._get_source(self.to_format)
1729
1937
        return source.get_stream(search)
1730
1938
 
1731
1939
    def _get_stream(self, repo, search):
1748
1956
            return self._real_stream(repo, search)
1749
1957
        client = repo._client
1750
1958
        medium = client._medium
1751
 
        if medium._is_remote_before((1, 13)):
1752
 
            # streaming was added in 1.13
1753
 
            return self._real_stream(repo, search)
1754
1959
        path = repo.bzrdir._path_for_remote_call(client)
1755
 
        try:
1756
 
            search_bytes = repo._serialise_search_result(search)
1757
 
            response = repo._call_with_body_bytes_expecting_body(
1758
 
                'Repository.get_stream',
1759
 
                (path, self.to_format.network_name()), search_bytes)
1760
 
            response_tuple, response_handler = response
1761
 
        except errors.UnknownSmartMethod:
1762
 
            medium._remember_remote_is_before((1,13))
 
1960
        search_bytes = repo._serialise_search_result(search)
 
1961
        args = (path, self.to_format.network_name())
 
1962
        candidate_verbs = [
 
1963
            ('Repository.get_stream_1.19', (1, 19)),
 
1964
            ('Repository.get_stream', (1, 13))]
 
1965
        found_verb = False
 
1966
        for verb, version in candidate_verbs:
 
1967
            if medium._is_remote_before(version):
 
1968
                continue
 
1969
            try:
 
1970
                response = repo._call_with_body_bytes_expecting_body(
 
1971
                    verb, args, search_bytes)
 
1972
            except errors.UnknownSmartMethod:
 
1973
                medium._remember_remote_is_before(version)
 
1974
            else:
 
1975
                response_tuple, response_handler = response
 
1976
                found_verb = True
 
1977
                break
 
1978
        if not found_verb:
1763
1979
            return self._real_stream(repo, search)
1764
1980
        if response_tuple[0] != 'ok':
1765
1981
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1766
1982
        byte_stream = response_handler.read_streamed_body()
1767
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
1983
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
1984
            self._record_counter)
1768
1985
        if src_format.network_name() != repo._format.network_name():
1769
1986
            raise AssertionError(
1770
1987
                "Mismatched RemoteRepository and stream src %r, %r" % (
1777
1994
        :param search: The overall search to satisfy with streams.
1778
1995
        :param sources: A list of Repository objects to query.
1779
1996
        """
1780
 
        self.serialiser = self.to_format._serializer
 
1997
        self.from_serialiser = self.from_repository._format._serializer
1781
1998
        self.seen_revs = set()
1782
1999
        self.referenced_revs = set()
1783
2000
        # If there are heads in the search, or the key count is > 0, we are not
1800
2017
    def missing_parents_rev_handler(self, substream):
1801
2018
        for content in substream:
1802
2019
            revision_bytes = content.get_bytes_as('fulltext')
1803
 
            revision = self.serialiser.read_revision_from_string(revision_bytes)
 
2020
            revision = self.from_serialiser.read_revision_from_string(
 
2021
                revision_bytes)
1804
2022
            self.seen_revs.add(content.key[-1])
1805
2023
            self.referenced_revs.update(revision.parent_ids)
1806
2024
            yield content
1845
2063
                self._network_name)
1846
2064
 
1847
2065
    def get_format_description(self):
1848
 
        return 'Remote BZR Branch'
 
2066
        self._ensure_real()
 
2067
        return 'Remote: ' + self._custom_format.get_format_description()
1849
2068
 
1850
2069
    def network_name(self):
1851
2070
        return self._network_name
1852
2071
 
1853
 
    def open(self, a_bzrdir, ignore_fallbacks=False):
1854
 
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
2072
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2073
        return a_bzrdir.open_branch(name=name, 
 
2074
            ignore_fallbacks=ignore_fallbacks)
1855
2075
 
1856
 
    def _vfs_initialize(self, a_bzrdir):
 
2076
    def _vfs_initialize(self, a_bzrdir, name):
1857
2077
        # Initialisation when using a local bzrdir object, or a non-vfs init
1858
2078
        # method is not available on the server.
1859
2079
        # self._custom_format is always set - the start of initialize ensures
1860
2080
        # that.
1861
2081
        if isinstance(a_bzrdir, RemoteBzrDir):
1862
2082
            a_bzrdir._ensure_real()
1863
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
2083
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2084
                name)
1864
2085
        else:
1865
2086
            # We assume the bzrdir is parameterised; it may not be.
1866
 
            result = self._custom_format.initialize(a_bzrdir)
 
2087
            result = self._custom_format.initialize(a_bzrdir, name)
1867
2088
        if (isinstance(a_bzrdir, RemoteBzrDir) and
1868
2089
            not isinstance(result, RemoteBranch)):
1869
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
2090
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
2091
                                  name=name)
1870
2092
        return result
1871
2093
 
1872
 
    def initialize(self, a_bzrdir):
 
2094
    def initialize(self, a_bzrdir, name=None):
1873
2095
        # 1) get the network name to use.
1874
2096
        if self._custom_format:
1875
2097
            network_name = self._custom_format.network_name()
1881
2103
            network_name = reference_format.network_name()
1882
2104
        # Being asked to create on a non RemoteBzrDir:
1883
2105
        if not isinstance(a_bzrdir, RemoteBzrDir):
1884
 
            return self._vfs_initialize(a_bzrdir)
 
2106
            return self._vfs_initialize(a_bzrdir, name=name)
1885
2107
        medium = a_bzrdir._client._medium
1886
2108
        if medium._is_remote_before((1, 13)):
1887
 
            return self._vfs_initialize(a_bzrdir)
 
2109
            return self._vfs_initialize(a_bzrdir, name=name)
1888
2110
        # Creating on a remote bzr dir.
1889
2111
        # 2) try direct creation via RPC
1890
2112
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2113
        if name is not None:
 
2114
            # XXX JRV20100304: Support creating colocated branches
 
2115
            raise errors.NoColocatedBranchSupport(self)
1891
2116
        verb = 'BzrDir.create_branch'
1892
2117
        try:
1893
2118
            response = a_bzrdir._call(verb, path, network_name)
1894
2119
        except errors.UnknownSmartMethod:
1895
2120
            # Fallback - use vfs methods
1896
2121
            medium._remember_remote_is_before((1, 13))
1897
 
            return self._vfs_initialize(a_bzrdir)
 
2122
            return self._vfs_initialize(a_bzrdir, name=name)
1898
2123
        if response[0] != 'ok':
1899
2124
            raise errors.UnexpectedSmartServerResponse(response)
1900
2125
        # Turn the response into a RemoteRepository object.
1908
2133
                a_bzrdir._client)
1909
2134
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1910
2135
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1911
 
            format=format, setup_stacking=False)
 
2136
            format=format, setup_stacking=False, name=name)
1912
2137
        # XXX: We know this is a new branch, so it must have revno 0, revid
1913
2138
        # NULL_REVISION. Creating the branch locked would make this be unable
1914
2139
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1934
2159
        return self._custom_format.supports_set_append_revisions_only()
1935
2160
 
1936
2161
 
1937
 
class RemoteBranch(branch.Branch, _RpcHelper):
 
2162
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1938
2163
    """Branch stored on a server accessed by HPSS RPC.
1939
2164
 
1940
2165
    At the moment most operations are mapped down to simple file operations.
1941
2166
    """
1942
2167
 
1943
2168
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1944
 
        _client=None, format=None, setup_stacking=True):
 
2169
        _client=None, format=None, setup_stacking=True, name=None):
1945
2170
        """Create a RemoteBranch instance.
1946
2171
 
1947
2172
        :param real_branch: An optional local implementation of the branch
1953
2178
        :param setup_stacking: If True make an RPC call to determine the
1954
2179
            stacked (or not) status of the branch. If False assume the branch
1955
2180
            is not stacked.
 
2181
        :param name: Colocated branch name
1956
2182
        """
1957
2183
        # We intentionally don't call the parent class's __init__, because it
1958
2184
        # will try to assign to self.tags, which is a property in this subclass.
1977
2203
            self._real_branch = None
1978
2204
        # Fill out expected attributes of branch for bzrlib API users.
1979
2205
        self._clear_cached_state()
1980
 
        self.base = self.bzrdir.root_transport.base
 
2206
        # TODO: deprecate self.base in favor of user_url
 
2207
        self.base = self.bzrdir.user_url
 
2208
        self._name = name
1981
2209
        self._control_files = None
1982
2210
        self._lock_mode = None
1983
2211
        self._lock_token = None
1994
2222
                    self._real_branch._format.network_name()
1995
2223
        else:
1996
2224
            self._format = format
 
2225
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
 
2226
        # branch.open_branch method.
 
2227
        self._real_ignore_fallbacks = not setup_stacking
1997
2228
        if not self._format._network_name:
1998
2229
            # Did not get from open_branchV2 - old server.
1999
2230
            self._ensure_real()
2044
2275
                raise AssertionError('smart server vfs must be enabled '
2045
2276
                    'to use vfs implementation')
2046
2277
            self.bzrdir._ensure_real()
2047
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
2278
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
2279
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2048
2280
            if self.repository._real_repository is None:
2049
2281
                # Give the remote repository the matching real repo.
2050
2282
                real_repo = self._real_branch.repository
2146
2378
            return self._vfs_get_tags_bytes()
2147
2379
        return response[0]
2148
2380
 
 
2381
    def _vfs_set_tags_bytes(self, bytes):
 
2382
        self._ensure_real()
 
2383
        return self._real_branch._set_tags_bytes(bytes)
 
2384
 
 
2385
    def _set_tags_bytes(self, bytes):
 
2386
        medium = self._client._medium
 
2387
        if medium._is_remote_before((1, 18)):
 
2388
            self._vfs_set_tags_bytes(bytes)
 
2389
            return
 
2390
        try:
 
2391
            args = (
 
2392
                self._remote_path(), self._lock_token, self._repo_lock_token)
 
2393
            response = self._call_with_body_bytes(
 
2394
                'Branch.set_tags_bytes', args, bytes)
 
2395
        except errors.UnknownSmartMethod:
 
2396
            medium._remember_remote_is_before((1, 18))
 
2397
            self._vfs_set_tags_bytes(bytes)
 
2398
 
2149
2399
    def lock_read(self):
 
2400
        """Lock the branch for read operations.
 
2401
 
 
2402
        :return: A bzrlib.lock.LogicalLockResult.
 
2403
        """
2150
2404
        self.repository.lock_read()
2151
2405
        if not self._lock_mode:
 
2406
            self._note_lock('r')
2152
2407
            self._lock_mode = 'r'
2153
2408
            self._lock_count = 1
2154
2409
            if self._real_branch is not None:
2155
2410
                self._real_branch.lock_read()
2156
2411
        else:
2157
2412
            self._lock_count += 1
 
2413
        return lock.LogicalLockResult(self.unlock)
2158
2414
 
2159
2415
    def _remote_lock_write(self, token):
2160
2416
        if token is None:
2161
2417
            branch_token = repo_token = ''
2162
2418
        else:
2163
2419
            branch_token = token
2164
 
            repo_token = self.repository.lock_write()
 
2420
            repo_token = self.repository.lock_write().repository_token
2165
2421
            self.repository.unlock()
2166
2422
        err_context = {'token': token}
2167
 
        response = self._call(
2168
 
            'Branch.lock_write', self._remote_path(), branch_token,
2169
 
            repo_token or '', **err_context)
 
2423
        try:
 
2424
            response = self._call(
 
2425
                'Branch.lock_write', self._remote_path(), branch_token,
 
2426
                repo_token or '', **err_context)
 
2427
        except errors.LockContention, e:
 
2428
            # The LockContention from the server doesn't have any
 
2429
            # information about the lock_url. We re-raise LockContention
 
2430
            # with valid lock_url.
 
2431
            raise errors.LockContention('(remote lock)',
 
2432
                self.repository.base.split('.bzr/')[0])
2170
2433
        if response[0] != 'ok':
2171
2434
            raise errors.UnexpectedSmartServerResponse(response)
2172
2435
        ok, branch_token, repo_token = response
2174
2437
 
2175
2438
    def lock_write(self, token=None):
2176
2439
        if not self._lock_mode:
 
2440
            self._note_lock('w')
2177
2441
            # Lock the branch and repo in one remote call.
2178
2442
            remote_tokens = self._remote_lock_write(token)
2179
2443
            self._lock_token, self._repo_lock_token = remote_tokens
2192
2456
            self._lock_mode = 'w'
2193
2457
            self._lock_count = 1
2194
2458
        elif self._lock_mode == 'r':
2195
 
            raise errors.ReadOnlyTransaction
 
2459
            raise errors.ReadOnlyError(self)
2196
2460
        else:
2197
2461
            if token is not None:
2198
2462
                # A token was given to lock_write, and we're relocking, so
2203
2467
            self._lock_count += 1
2204
2468
            # Re-lock the repository too.
2205
2469
            self.repository.lock_write(self._repo_lock_token)
2206
 
        return self._lock_token or None
2207
 
 
2208
 
    def _set_tags_bytes(self, bytes):
2209
 
        self._ensure_real()
2210
 
        return self._real_branch._set_tags_bytes(bytes)
 
2470
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2211
2471
 
2212
2472
    def _unlock(self, branch_token, repo_token):
2213
2473
        err_context = {'token': str((branch_token, repo_token))}
2218
2478
            return
2219
2479
        raise errors.UnexpectedSmartServerResponse(response)
2220
2480
 
 
2481
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2221
2482
    def unlock(self):
2222
2483
        try:
2223
2484
            self._lock_count -= 1
2263
2524
            raise NotImplementedError(self.dont_leave_lock_in_place)
2264
2525
        self._leave_lock = False
2265
2526
 
 
2527
    @needs_read_lock
2266
2528
    def get_rev_id(self, revno, history=None):
2267
2529
        if revno == 0:
2268
2530
            return _mod_revision.NULL_REVISION
2534
2796
        medium = self._branch._client._medium
2535
2797
        if medium._is_remote_before((1, 14)):
2536
2798
            return self._vfs_set_option(value, name, section)
 
2799
        if isinstance(value, dict):
 
2800
            if medium._is_remote_before((2, 2)):
 
2801
                return self._vfs_set_option(value, name, section)
 
2802
            return self._set_config_option_dict(value, name, section)
 
2803
        else:
 
2804
            return self._set_config_option(value, name, section)
 
2805
 
 
2806
    def _set_config_option(self, value, name, section):
2537
2807
        try:
2538
2808
            path = self._branch._remote_path()
2539
2809
            response = self._branch._client.call('Branch.set_config_option',
2540
2810
                path, self._branch._lock_token, self._branch._repo_lock_token,
2541
2811
                value.encode('utf8'), name, section or '')
2542
2812
        except errors.UnknownSmartMethod:
 
2813
            medium = self._branch._client._medium
2543
2814
            medium._remember_remote_is_before((1, 14))
2544
2815
            return self._vfs_set_option(value, name, section)
2545
2816
        if response != ():
2546
2817
            raise errors.UnexpectedSmartServerResponse(response)
2547
2818
 
 
2819
    def _serialize_option_dict(self, option_dict):
 
2820
        utf8_dict = {}
 
2821
        for key, value in option_dict.items():
 
2822
            if isinstance(key, unicode):
 
2823
                key = key.encode('utf8')
 
2824
            if isinstance(value, unicode):
 
2825
                value = value.encode('utf8')
 
2826
            utf8_dict[key] = value
 
2827
        return bencode.bencode(utf8_dict)
 
2828
 
 
2829
    def _set_config_option_dict(self, value, name, section):
 
2830
        try:
 
2831
            path = self._branch._remote_path()
 
2832
            serialised_dict = self._serialize_option_dict(value)
 
2833
            response = self._branch._client.call(
 
2834
                'Branch.set_config_option_dict',
 
2835
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2836
                serialised_dict, name, section or '')
 
2837
        except errors.UnknownSmartMethod:
 
2838
            medium = self._branch._client._medium
 
2839
            medium._remember_remote_is_before((2, 2))
 
2840
            return self._vfs_set_option(value, name, section)
 
2841
        if response != ():
 
2842
            raise errors.UnexpectedSmartServerResponse(response)
 
2843
 
2548
2844
    def _real_object(self):
2549
2845
        self._branch._ensure_real()
2550
2846
        return self._branch._real_branch
2633
2929
                    'Missing key %r in context %r', key_err.args[0], context)
2634
2930
                raise err
2635
2931
 
2636
 
    if err.error_verb == 'NoSuchRevision':
 
2932
    if err.error_verb == 'IncompatibleRepositories':
 
2933
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2934
            err.error_args[1], err.error_args[2])
 
2935
    elif err.error_verb == 'NoSuchRevision':
2637
2936
        raise NoSuchRevision(find('branch'), err.error_args[0])
2638
2937
    elif err.error_verb == 'nosuchrevision':
2639
2938
        raise NoSuchRevision(find('repository'), err.error_args[0])
2640
 
    elif err.error_tuple == ('nobranch',):
2641
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2939
    elif err.error_verb == 'nobranch':
 
2940
        if len(err.error_args) >= 1:
 
2941
            extra = err.error_args[0]
 
2942
        else:
 
2943
            extra = None
 
2944
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2945
            detail=extra)
2642
2946
    elif err.error_verb == 'norepository':
2643
2947
        raise errors.NoRepositoryPresent(find('bzrdir'))
2644
2948
    elif err.error_verb == 'LockContention':