~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Pool
  • Date: 2010-07-21 09:58:42 UTC
  • mfrom: (4797.58.7 2.1)
  • mto: (5050.3.13 2.2)
  • mto: This revision was merged to the branch mainline in revision 5365.
  • Revision ID: mbp@canonical.com-20100721095842-hz0obu8gl0x05nty
merge up 2.1 to 2.2

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
566
637
        return self._creating_repo._real_repository._format.network_name()
567
638
 
568
639
    @property
 
640
    def pack_compresses(self):
 
641
        self._ensure_real()
 
642
        return self._custom_format.pack_compresses
 
643
 
 
644
    @property
569
645
    def _serializer(self):
570
646
        self._ensure_real()
571
647
        return self._custom_format._serializer
572
648
 
573
649
 
574
 
class RemoteRepository(_RpcHelper):
 
650
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
 
651
    bzrdir.ControlComponent):
575
652
    """Repository accessed over rpc.
576
653
 
577
654
    For the moment most operations are performed using local transport-backed
620
697
        # Additional places to query for data.
621
698
        self._fallback_repositories = []
622
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
        
623
711
    def __str__(self):
624
712
        return "%s(%s)" % (self.__class__.__name__, self.base)
625
713
 
718
806
        invocation. If in doubt chat to the bzr network team.
719
807
        """
720
808
        if self._real_repository is None:
721
 
            if 'hpss' in debug.debug_flags:
 
809
            if 'hpssvfs' in debug.debug_flags:
722
810
                import traceback
723
811
                warning('VFS Repository access triggered\n%s',
724
812
                    ''.join(traceback.format_stack()))
808
896
            result.add(_mod_revision.NULL_REVISION)
809
897
        return result
810
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
 
811
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
812
916
        return (self.__class__ is other.__class__ and
813
917
                self.bzrdir.transport.base == other.bzrdir.transport.base)
814
918
 
817
921
        parents_provider = self._make_parents_provider(other_repository)
818
922
        return graph.Graph(parents_provider)
819
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
 
820
933
    def gather_stats(self, revid=None, committers=None):
821
934
        """See Repository.gather_stats()."""
822
935
        path = self.bzrdir._path_for_remote_call(self._client)
882
995
    def is_write_locked(self):
883
996
        return self._lock_mode == 'w'
884
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
 
885
1003
    def lock_read(self):
 
1004
        """Lock the repository for read operations.
 
1005
 
 
1006
        :return: A bzrlib.lock.LogicalLockResult.
 
1007
        """
886
1008
        # wrong eventually - want a local lock cache context
887
1009
        if not self._lock_mode:
 
1010
            self._note_lock('r')
888
1011
            self._lock_mode = 'r'
889
1012
            self._lock_count = 1
890
1013
            self._unstacked_provider.enable_cache(cache_misses=True)
894
1017
                repo.lock_read()
895
1018
        else:
896
1019
            self._lock_count += 1
 
1020
        return lock.LogicalLockResult(self.unlock)
897
1021
 
898
1022
    def _remote_lock_write(self, token):
899
1023
        path = self.bzrdir._path_for_remote_call(self._client)
910
1034
 
911
1035
    def lock_write(self, token=None, _skip_rpc=False):
912
1036
        if not self._lock_mode:
 
1037
            self._note_lock('w')
913
1038
            if _skip_rpc:
914
1039
                if self._lock_token is not None:
915
1040
                    if token != self._lock_token:
938
1063
            raise errors.ReadOnlyError(self)
939
1064
        else:
940
1065
            self._lock_count += 1
941
 
        return self._lock_token or None
 
1066
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
942
1067
 
943
1068
    def leave_lock_in_place(self):
944
1069
        if not self._lock_token:
1018
1143
        else:
1019
1144
            raise errors.UnexpectedSmartServerResponse(response)
1020
1145
 
 
1146
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1021
1147
    def unlock(self):
1022
1148
        if not self._lock_count:
1023
 
            raise errors.LockNotHeld(self)
 
1149
            return lock.cant_unlock_not_held(self)
1024
1150
        self._lock_count -= 1
1025
1151
        if self._lock_count > 0:
1026
1152
            return
1123
1249
            # state, so always add a lock here. If a caller passes us a locked
1124
1250
            # repository, they are responsible for unlocking it later.
1125
1251
            repository.lock_read()
 
1252
        self._check_fallback_repository(repository)
1126
1253
        self._fallback_repositories.append(repository)
1127
1254
        # If self._real_repository was parameterised already (e.g. because a
1128
1255
        # _real_branch had its get_stacked_on_url method called), then the
1129
1256
        # repository to be added may already be in the _real_repositories list.
1130
1257
        if self._real_repository is not None:
1131
 
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
 
1258
            fallback_locations = [repo.user_url for repo in
1132
1259
                self._real_repository._fallback_repositories]
1133
 
            if repository.bzrdir.root_transport.base not in fallback_locations:
 
1260
            if repository.user_url not in fallback_locations:
1134
1261
                self._real_repository.add_fallback_repository(repository)
1135
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
 
1136
1273
    def add_inventory(self, revid, inv, parents):
1137
1274
        self._ensure_real()
1138
1275
        return self._real_repository.add_inventory(revid, inv, parents)
1139
1276
 
1140
1277
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1141
 
                               parents):
 
1278
            parents, basis_inv=None, propagate_caches=False):
1142
1279
        self._ensure_real()
1143
1280
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1144
 
            delta, new_revision_id, parents)
 
1281
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1282
            propagate_caches=propagate_caches)
1145
1283
 
1146
1284
    def add_revision(self, rev_id, rev, inv=None, config=None):
1147
1285
        self._ensure_real()
1153
1291
        self._ensure_real()
1154
1292
        return self._real_repository.get_inventory(revision_id)
1155
1293
 
1156
 
    def iter_inventories(self, revision_ids):
 
1294
    def iter_inventories(self, revision_ids, ordering=None):
1157
1295
        self._ensure_real()
1158
 
        return self._real_repository.iter_inventories(revision_ids)
 
1296
        return self._real_repository.iter_inventories(revision_ids, ordering)
1159
1297
 
1160
1298
    @needs_read_lock
1161
1299
    def get_revision(self, revision_id):
1177
1315
        return self._real_repository.make_working_trees()
1178
1316
 
1179
1317
    def refresh_data(self):
1180
 
        """Re-read any data needed to to synchronise with disk.
 
1318
        """Re-read any data needed to synchronise with disk.
1181
1319
 
1182
1320
        This method is intended to be called after another repository instance
1183
1321
        (such as one used by a smart server) has inserted data into the
1184
 
        repository. It may not be called during a write group, but may be
1185
 
        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.
1186
1327
        """
1187
 
        if self.is_in_write_group():
1188
 
            raise errors.InternalBzrError(
1189
 
                "May not refresh_data while in a write group.")
1190
1328
        if self._real_repository is not None:
1191
1329
            self._real_repository.refresh_data()
1192
1330
 
1225
1363
            raise errors.InternalBzrError(
1226
1364
                "May not fetch while in a write group.")
1227
1365
        # fast path same-url fetch operations
1228
 
        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)):
1229
1369
            # check that last_revision is in 'from' and then return a
1230
1370
            # no-operation.
1231
1371
            if (revision_id is not None and
1404
1544
        return self._real_repository.get_signature_text(revision_id)
1405
1545
 
1406
1546
    @needs_read_lock
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)
 
1547
    def _get_inventory_xml(self, revision_id):
 
1548
        self._ensure_real()
 
1549
        return self._real_repository._get_inventory_xml(revision_id)
1414
1550
 
1415
1551
    def reconcile(self, other=None, thorough=False):
1416
1552
        self._ensure_real()
1443
1579
        return self._real_repository.get_revision_reconcile(revision_id)
1444
1580
 
1445
1581
    @needs_read_lock
1446
 
    def check(self, revision_ids=None):
 
1582
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1447
1583
        self._ensure_real()
1448
 
        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)
1449
1586
 
1450
1587
    def copy_content_into(self, destination, revision_id=None):
1451
1588
        self._ensure_real()
1491
1628
        return self._real_repository.inventories
1492
1629
 
1493
1630
    @needs_write_lock
1494
 
    def pack(self):
 
1631
    def pack(self, hint=None, clean_obsolete_packs=False):
1495
1632
        """Compress the data within the repository.
1496
1633
 
1497
1634
        This is not currently implemented within the smart server.
1498
1635
        """
1499
1636
        self._ensure_real()
1500
 
        return self._real_repository.pack()
 
1637
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1501
1638
 
1502
1639
    @property
1503
1640
    def revisions(self):
1591
1728
        self._ensure_real()
1592
1729
        return self._real_repository.revision_graph_can_have_wrong_parents()
1593
1730
 
1594
 
    def _find_inconsistent_revision_parents(self):
 
1731
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1595
1732
        self._ensure_real()
1596
 
        return self._real_repository._find_inconsistent_revision_parents()
 
1733
        return self._real_repository._find_inconsistent_revision_parents(
 
1734
            revisions_iterator)
1597
1735
 
1598
1736
    def _check_for_inconsistent_revision_parents(self):
1599
1737
        self._ensure_real()
1653
1791
    def insert_stream(self, stream, src_format, resume_tokens):
1654
1792
        target = self.target_repo
1655
1793
        target._unstacked_provider.missing_keys.clear()
 
1794
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1656
1795
        if target._lock_token:
1657
 
            verb = 'Repository.insert_stream_locked'
1658
 
            extra_args = (target._lock_token or '',)
1659
 
            required_version = (1, 14)
 
1796
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1797
            lock_args = (target._lock_token or '',)
1660
1798
        else:
1661
 
            verb = 'Repository.insert_stream'
1662
 
            extra_args = ()
1663
 
            required_version = (1, 13)
 
1799
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
1800
            lock_args = ()
1664
1801
        client = target._client
1665
1802
        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)
1669
1803
        path = target.bzrdir._path_for_remote_call(client)
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.
 
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
1679
1817
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1680
1818
            try:
1681
1819
                response = client.call_with_body_stream(
1682
 
                    (verb, path, '') + extra_args, byte_stream)
 
1820
                    (verb, path, '') + lock_args, byte_stream)
1683
1821
            except errors.UnknownSmartMethod:
1684
1822
                medium._remember_remote_is_before(required_version)
1685
 
                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)
1686
1837
        byte_stream = smart_repo._stream_to_byte_stream(
1687
1838
            stream, src_format)
1688
1839
        resume_tokens = ' '.join(resume_tokens)
1689
1840
        response = client.call_with_body_stream(
1690
 
            (verb, path, resume_tokens) + extra_args, byte_stream)
 
1841
            (verb, path, resume_tokens) + lock_args, byte_stream)
1691
1842
        if response[0][0] not in ('ok', 'missing-basis'):
1692
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)
1693
1850
        if response[0][0] == 'missing-basis':
1694
1851
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1695
1852
            resume_tokens = tokens
1698
1855
            self.target_repo.refresh_data()
1699
1856
            return [], set()
1700
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
            
1701
1898
 
1702
1899
class RemoteStreamSource(repository.StreamSource):
1703
1900
    """Stream data from a remote server."""
1706
1903
        if (self.from_repository._fallback_repositories and
1707
1904
            self.to_format._fetch_order == 'topological'):
1708
1905
            return self._real_stream(self.from_repository, search)
1709
 
        return self.missing_parents_chain(search, [self.from_repository] +
1710
 
            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)
1711
1923
 
1712
1924
    def _real_stream(self, repo, search):
1713
1925
        """Get a stream for search from repo.
1720
1932
        """
1721
1933
        source = repo._get_source(self.to_format)
1722
1934
        if isinstance(source, RemoteStreamSource):
1723
 
            return repository.StreamSource.get_stream(source, search)
 
1935
            repo._ensure_real()
 
1936
            source = repo._real_repository._get_source(self.to_format)
1724
1937
        return source.get_stream(search)
1725
1938
 
1726
1939
    def _get_stream(self, repo, search):
1743
1956
            return self._real_stream(repo, search)
1744
1957
        client = repo._client
1745
1958
        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)
1749
1959
        path = repo.bzrdir._path_for_remote_call(client)
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))
 
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:
1758
1979
            return self._real_stream(repo, search)
1759
1980
        if response_tuple[0] != 'ok':
1760
1981
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1761
1982
        byte_stream = response_handler.read_streamed_body()
1762
 
        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)
1763
1985
        if src_format.network_name() != repo._format.network_name():
1764
1986
            raise AssertionError(
1765
1987
                "Mismatched RemoteRepository and stream src %r, %r" % (
1772
1994
        :param search: The overall search to satisfy with streams.
1773
1995
        :param sources: A list of Repository objects to query.
1774
1996
        """
1775
 
        self.serialiser = self.to_format._serializer
 
1997
        self.from_serialiser = self.from_repository._format._serializer
1776
1998
        self.seen_revs = set()
1777
1999
        self.referenced_revs = set()
1778
2000
        # If there are heads in the search, or the key count is > 0, we are not
1795
2017
    def missing_parents_rev_handler(self, substream):
1796
2018
        for content in substream:
1797
2019
            revision_bytes = content.get_bytes_as('fulltext')
1798
 
            revision = self.serialiser.read_revision_from_string(revision_bytes)
 
2020
            revision = self.from_serialiser.read_revision_from_string(
 
2021
                revision_bytes)
1799
2022
            self.seen_revs.add(content.key[-1])
1800
2023
            self.referenced_revs.update(revision.parent_ids)
1801
2024
            yield content
1840
2063
                self._network_name)
1841
2064
 
1842
2065
    def get_format_description(self):
1843
 
        return 'Remote BZR Branch'
 
2066
        self._ensure_real()
 
2067
        return 'Remote: ' + self._custom_format.get_format_description()
1844
2068
 
1845
2069
    def network_name(self):
1846
2070
        return self._network_name
1847
2071
 
1848
 
    def open(self, a_bzrdir, ignore_fallbacks=False):
1849
 
        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)
1850
2075
 
1851
 
    def _vfs_initialize(self, a_bzrdir):
 
2076
    def _vfs_initialize(self, a_bzrdir, name):
1852
2077
        # Initialisation when using a local bzrdir object, or a non-vfs init
1853
2078
        # method is not available on the server.
1854
2079
        # self._custom_format is always set - the start of initialize ensures
1855
2080
        # that.
1856
2081
        if isinstance(a_bzrdir, RemoteBzrDir):
1857
2082
            a_bzrdir._ensure_real()
1858
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
 
2083
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2084
                name)
1859
2085
        else:
1860
2086
            # We assume the bzrdir is parameterised; it may not be.
1861
 
            result = self._custom_format.initialize(a_bzrdir)
 
2087
            result = self._custom_format.initialize(a_bzrdir, name)
1862
2088
        if (isinstance(a_bzrdir, RemoteBzrDir) and
1863
2089
            not isinstance(result, RemoteBranch)):
1864
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
 
2090
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
2091
                                  name=name)
1865
2092
        return result
1866
2093
 
1867
 
    def initialize(self, a_bzrdir):
 
2094
    def initialize(self, a_bzrdir, name=None):
1868
2095
        # 1) get the network name to use.
1869
2096
        if self._custom_format:
1870
2097
            network_name = self._custom_format.network_name()
1876
2103
            network_name = reference_format.network_name()
1877
2104
        # Being asked to create on a non RemoteBzrDir:
1878
2105
        if not isinstance(a_bzrdir, RemoteBzrDir):
1879
 
            return self._vfs_initialize(a_bzrdir)
 
2106
            return self._vfs_initialize(a_bzrdir, name=name)
1880
2107
        medium = a_bzrdir._client._medium
1881
2108
        if medium._is_remote_before((1, 13)):
1882
 
            return self._vfs_initialize(a_bzrdir)
 
2109
            return self._vfs_initialize(a_bzrdir, name=name)
1883
2110
        # Creating on a remote bzr dir.
1884
2111
        # 2) try direct creation via RPC
1885
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)
1886
2116
        verb = 'BzrDir.create_branch'
1887
2117
        try:
1888
2118
            response = a_bzrdir._call(verb, path, network_name)
1889
2119
        except errors.UnknownSmartMethod:
1890
2120
            # Fallback - use vfs methods
1891
2121
            medium._remember_remote_is_before((1, 13))
1892
 
            return self._vfs_initialize(a_bzrdir)
 
2122
            return self._vfs_initialize(a_bzrdir, name=name)
1893
2123
        if response[0] != 'ok':
1894
2124
            raise errors.UnexpectedSmartServerResponse(response)
1895
2125
        # Turn the response into a RemoteRepository object.
1903
2133
                a_bzrdir._client)
1904
2134
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1905
2135
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1906
 
            format=format, setup_stacking=False)
 
2136
            format=format, setup_stacking=False, name=name)
1907
2137
        # XXX: We know this is a new branch, so it must have revno 0, revid
1908
2138
        # NULL_REVISION. Creating the branch locked would make this be unable
1909
2139
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1929
2159
        return self._custom_format.supports_set_append_revisions_only()
1930
2160
 
1931
2161
 
1932
 
class RemoteBranch(branch.Branch, _RpcHelper):
 
2162
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1933
2163
    """Branch stored on a server accessed by HPSS RPC.
1934
2164
 
1935
2165
    At the moment most operations are mapped down to simple file operations.
1936
2166
    """
1937
2167
 
1938
2168
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1939
 
        _client=None, format=None, setup_stacking=True):
 
2169
        _client=None, format=None, setup_stacking=True, name=None):
1940
2170
        """Create a RemoteBranch instance.
1941
2171
 
1942
2172
        :param real_branch: An optional local implementation of the branch
1948
2178
        :param setup_stacking: If True make an RPC call to determine the
1949
2179
            stacked (or not) status of the branch. If False assume the branch
1950
2180
            is not stacked.
 
2181
        :param name: Colocated branch name
1951
2182
        """
1952
2183
        # We intentionally don't call the parent class's __init__, because it
1953
2184
        # will try to assign to self.tags, which is a property in this subclass.
1972
2203
            self._real_branch = None
1973
2204
        # Fill out expected attributes of branch for bzrlib API users.
1974
2205
        self._clear_cached_state()
1975
 
        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
1976
2209
        self._control_files = None
1977
2210
        self._lock_mode = None
1978
2211
        self._lock_token = None
1989
2222
                    self._real_branch._format.network_name()
1990
2223
        else:
1991
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
1992
2228
        if not self._format._network_name:
1993
2229
            # Did not get from open_branchV2 - old server.
1994
2230
            self._ensure_real()
2039
2275
                raise AssertionError('smart server vfs must be enabled '
2040
2276
                    'to use vfs implementation')
2041
2277
            self.bzrdir._ensure_real()
2042
 
            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)
2043
2280
            if self.repository._real_repository is None:
2044
2281
                # Give the remote repository the matching real repo.
2045
2282
                real_repo = self._real_branch.repository
2141
2378
            return self._vfs_get_tags_bytes()
2142
2379
        return response[0]
2143
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
 
2144
2399
    def lock_read(self):
 
2400
        """Lock the branch for read operations.
 
2401
 
 
2402
        :return: A bzrlib.lock.LogicalLockResult.
 
2403
        """
2145
2404
        self.repository.lock_read()
2146
2405
        if not self._lock_mode:
 
2406
            self._note_lock('r')
2147
2407
            self._lock_mode = 'r'
2148
2408
            self._lock_count = 1
2149
2409
            if self._real_branch is not None:
2150
2410
                self._real_branch.lock_read()
2151
2411
        else:
2152
2412
            self._lock_count += 1
 
2413
        return lock.LogicalLockResult(self.unlock)
2153
2414
 
2154
2415
    def _remote_lock_write(self, token):
2155
2416
        if token is None:
2156
2417
            branch_token = repo_token = ''
2157
2418
        else:
2158
2419
            branch_token = token
2159
 
            repo_token = self.repository.lock_write()
 
2420
            repo_token = self.repository.lock_write().repository_token
2160
2421
            self.repository.unlock()
2161
2422
        err_context = {'token': token}
2162
 
        response = self._call(
2163
 
            'Branch.lock_write', self._remote_path(), branch_token,
2164
 
            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])
2165
2433
        if response[0] != 'ok':
2166
2434
            raise errors.UnexpectedSmartServerResponse(response)
2167
2435
        ok, branch_token, repo_token = response
2169
2437
 
2170
2438
    def lock_write(self, token=None):
2171
2439
        if not self._lock_mode:
 
2440
            self._note_lock('w')
2172
2441
            # Lock the branch and repo in one remote call.
2173
2442
            remote_tokens = self._remote_lock_write(token)
2174
2443
            self._lock_token, self._repo_lock_token = remote_tokens
2187
2456
            self._lock_mode = 'w'
2188
2457
            self._lock_count = 1
2189
2458
        elif self._lock_mode == 'r':
2190
 
            raise errors.ReadOnlyTransaction
 
2459
            raise errors.ReadOnlyError(self)
2191
2460
        else:
2192
2461
            if token is not None:
2193
2462
                # A token was given to lock_write, and we're relocking, so
2198
2467
            self._lock_count += 1
2199
2468
            # Re-lock the repository too.
2200
2469
            self.repository.lock_write(self._repo_lock_token)
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)
 
2470
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2206
2471
 
2207
2472
    def _unlock(self, branch_token, repo_token):
2208
2473
        err_context = {'token': str((branch_token, repo_token))}
2213
2478
            return
2214
2479
        raise errors.UnexpectedSmartServerResponse(response)
2215
2480
 
 
2481
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2216
2482
    def unlock(self):
2217
2483
        try:
2218
2484
            self._lock_count -= 1
2258
2524
            raise NotImplementedError(self.dont_leave_lock_in_place)
2259
2525
        self._leave_lock = False
2260
2526
 
 
2527
    @needs_read_lock
2261
2528
    def get_rev_id(self, revno, history=None):
2262
2529
        if revno == 0:
2263
2530
            return _mod_revision.NULL_REVISION
2529
2796
        medium = self._branch._client._medium
2530
2797
        if medium._is_remote_before((1, 14)):
2531
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):
2532
2807
        try:
2533
2808
            path = self._branch._remote_path()
2534
2809
            response = self._branch._client.call('Branch.set_config_option',
2535
2810
                path, self._branch._lock_token, self._branch._repo_lock_token,
2536
2811
                value.encode('utf8'), name, section or '')
2537
2812
        except errors.UnknownSmartMethod:
 
2813
            medium = self._branch._client._medium
2538
2814
            medium._remember_remote_is_before((1, 14))
2539
2815
            return self._vfs_set_option(value, name, section)
2540
2816
        if response != ():
2541
2817
            raise errors.UnexpectedSmartServerResponse(response)
2542
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
 
2543
2844
    def _real_object(self):
2544
2845
        self._branch._ensure_real()
2545
2846
        return self._branch._real_branch
2628
2929
                    'Missing key %r in context %r', key_err.args[0], context)
2629
2930
                raise err
2630
2931
 
2631
 
    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':
2632
2936
        raise NoSuchRevision(find('branch'), err.error_args[0])
2633
2937
    elif err.error_verb == 'nosuchrevision':
2634
2938
        raise NoSuchRevision(find('repository'), err.error_args[0])
2635
 
    elif err.error_tuple == ('nobranch',):
2636
 
        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)
2637
2946
    elif err.error_verb == 'norepository':
2638
2947
        raise errors.NoRepositoryPresent(find('bzrdir'))
2639
2948
    elif err.error_verb == 'LockContention':