~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Pool
  • Date: 2009-07-10 06:46:10 UTC
  • mto: (4525.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4526.
  • Revision ID: mbp@sourcefrog.net-20090710064610-sqviksbqp5i34sw2
Rename to per_interrepository

Show diffs side-by-side

added added

removed removed

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