~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-29 22:03:03 UTC
  • mfrom: (5416.2.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100929220303-cr95h8iwtggco721
(mbp) Add 'break-lock --force'

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