33
33
from bzrlib.branch import BranchReferenceFormat
34
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
from bzrlib.config import BranchConfig, TreeConfig
35
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
from bzrlib.errors import (
37
from bzrlib.errors import NoSuchRevision
40
38
from bzrlib.lockable_files import LockableFiles
39
from bzrlib.pack import ContainerPushParser
41
40
from bzrlib.smart import client, vfs
42
from bzrlib.revision import ensure_null, NULL_REVISION
41
from bzrlib.symbol_versioning import (
45
from bzrlib.revision import NULL_REVISION
43
46
from bzrlib.trace import mutter, note, warning
46
class _RpcHelper(object):
47
"""Mixin class that helps with issuing RPCs."""
49
def _call(self, method, *args, **err_context):
51
return self._client.call(method, *args)
52
except errors.ErrorFromSmartServer, err:
53
self._translate_error(err, **err_context)
55
def _call_expecting_body(self, method, *args, **err_context):
57
return self._client.call_expecting_body(method, *args)
58
except errors.ErrorFromSmartServer, err:
59
self._translate_error(err, **err_context)
61
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
64
return self._client.call_with_body_bytes_expecting_body(
65
method, args, body_bytes)
66
except errors.ErrorFromSmartServer, err:
67
self._translate_error(err, **err_context)
69
48
# Note: RemoteBzrDirFormat is in bzrdir.py
71
class RemoteBzrDir(BzrDir, _RpcHelper):
50
class RemoteBzrDir(BzrDir):
72
51
"""Control directory on a remote server, accessed via bzr:// or similar."""
74
53
def __init__(self, transport, _client=None):
176
150
def open_repository(self):
177
151
path = self._path_for_remote_call(self._client)
178
152
verb = 'BzrDir.find_repositoryV2'
180
response = self._call(verb, path)
181
except errors.UnknownSmartMethod:
153
response = self._client.call(verb, path)
154
if (response == ('error', "Generic bzr smart protocol error: "
155
"bad request '%s'" % verb) or
156
response == ('error', "Generic bzr smart protocol error: "
157
"bad request u'%s'" % verb)):
182
158
verb = 'BzrDir.find_repository'
183
response = self._call(verb, path)
184
if response[0] != 'ok':
185
raise errors.UnexpectedSmartServerResponse(response)
159
response = self._client.call(verb, path)
160
assert response[0] in ('ok', 'norepository'), \
161
'unexpected response code %s' % (response,)
162
if response[0] == 'norepository':
163
raise errors.NoRepositoryPresent(self)
186
164
if verb == 'BzrDir.find_repository':
187
165
# servers that don't support the V2 method don't support external
188
166
# references either.
189
167
response = response + ('no', )
190
if not (len(response) == 5):
191
raise SmartProtocolError('incorrect response length %s' % (response,))
168
assert len(response) == 5, 'incorrect response length %s' % (response,)
192
169
if response[1] == '':
193
170
format = RemoteRepositoryFormat()
194
171
format.rich_root_data = (response[2] == 'yes')
195
172
format.supports_tree_reference = (response[3] == 'yes')
196
173
# No wire format to check this yet.
197
174
format.supports_external_lookups = (response[4] == 'yes')
198
# Used to support creating a real format instance when needed.
199
format._creating_bzrdir = self
200
175
return RemoteRepository(self, format)
202
177
raise errors.NoRepositoryPresent(self)
259
_matchingbzrdir = RemoteBzrDirFormat()
229
_matchingbzrdir = RemoteBzrDirFormat
261
231
def initialize(self, a_bzrdir, shared=False):
262
if not isinstance(a_bzrdir, RemoteBzrDir):
263
prior_repo = self._creating_bzrdir.open_repository()
264
prior_repo._ensure_real()
265
return prior_repo._real_repository._format.initialize(
266
a_bzrdir, shared=shared)
232
assert isinstance(a_bzrdir, RemoteBzrDir), \
233
'%r is not a RemoteBzrDir' % (a_bzrdir,)
267
234
return a_bzrdir.create_repository(shared=shared)
269
236
def open(self, a_bzrdir):
270
if not isinstance(a_bzrdir, RemoteBzrDir):
271
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
237
assert isinstance(a_bzrdir, RemoteBzrDir)
272
238
return a_bzrdir.open_repository()
274
240
def get_format_description(self):
332
298
self._reconcile_fixes_text_parents = False
333
299
self._reconcile_backsup_inventory = False
334
300
self.base = self.bzrdir.transport.base
335
# Additional places to query for data.
336
self._fallback_repositories = []
338
302
def __str__(self):
339
303
return "%s(%s)" % (self.__class__.__name__, self.base)
341
305
__repr__ = __str__
343
def abort_write_group(self, suppress_errors=False):
307
def abort_write_group(self):
344
308
"""Complete a write group on the decorated repository.
346
310
Smart methods peform operations in a single step so this api
347
311
is not really applicable except as a compatibility thunk
348
312
for older plugins that don't use e.g. the CommitBuilder
351
:param suppress_errors: see Repository.abort_write_group.
353
315
self._ensure_real()
354
return self._real_repository.abort_write_group(
355
suppress_errors=suppress_errors)
316
return self._real_repository.abort_write_group()
357
318
def commit_write_group(self):
358
319
"""Complete a write group on the decorated repository.
371
332
Used before calls to self._real_repository.
373
if self._real_repository is None:
334
if not self._real_repository:
374
335
self.bzrdir._ensure_real()
375
self._set_real_repository(
376
self.bzrdir._real_bzrdir.open_repository())
378
def _translate_error(self, err, **context):
379
self.bzrdir._translate_error(err, repository=self, **context)
336
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
337
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
381
339
def find_text_key_references(self):
382
340
"""Find the text key references within the repository.
403
361
self._ensure_real()
404
362
return self._real_repository._generate_text_key_index()
406
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
407
364
def get_revision_graph(self, revision_id=None):
408
365
"""See Repository.get_revision_graph()."""
409
return self._get_revision_graph(revision_id)
411
def _get_revision_graph(self, revision_id):
412
"""Private method for using with old (< 1.2) servers to fallback."""
413
366
if revision_id is None:
415
368
elif revision.is_null(revision_id):
418
371
path = self.bzrdir._path_for_remote_call(self._client)
419
response = self._call_expecting_body(
372
assert type(revision_id) is str
373
response = self._client.call_expecting_body(
420
374
'Repository.get_revision_graph', path, revision_id)
421
response_tuple, response_handler = response
422
if response_tuple[0] != 'ok':
423
raise errors.UnexpectedSmartServerResponse(response_tuple)
424
coded = response_handler.read_body_bytes()
426
# no revisions in this repository!
428
lines = coded.split('\n')
431
d = tuple(line.split())
432
revision_graph[d[0]] = d[1:]
434
return revision_graph
375
if response[0][0] not in ['ok', 'nosuchrevision']:
376
raise errors.UnexpectedSmartServerResponse(response[0])
377
if response[0][0] == 'ok':
378
coded = response[1].read_body_bytes()
380
# no revisions in this repository!
382
lines = coded.split('\n')
385
d = tuple(line.split())
386
revision_graph[d[0]] = d[1:]
388
return revision_graph
390
response_body = response[1].read_body_bytes()
391
assert response_body == ''
392
raise NoSuchRevision(self, revision_id)
436
394
def has_revision(self, revision_id):
437
395
"""See Repository.has_revision()."""
439
397
# The null revision is always present.
441
399
path = self.bzrdir._path_for_remote_call(self._client)
442
response = self._call('Repository.has_revision', path, revision_id)
443
if response[0] not in ('yes', 'no'):
444
raise errors.UnexpectedSmartServerResponse(response)
445
if response[0] == 'yes':
447
for fallback_repo in self._fallback_repositories:
448
if fallback_repo.has_revision(revision_id):
400
response = self._client.call('Repository.has_revision', path, revision_id)
401
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
402
return response[0] == 'yes'
452
404
def has_revisions(self, revision_ids):
453
405
"""See Repository.has_revisions()."""
454
# FIXME: This does many roundtrips, particularly when there are
455
# fallback repositories. -- mbp 20080905
457
407
for revision_id in revision_ids:
458
408
if self.has_revision(revision_id):
462
412
def has_same_location(self, other):
463
413
return (self.__class__ == other.__class__ and
464
414
self.bzrdir.transport.base == other.bzrdir.transport.base)
466
416
def get_graph(self, other_repository=None):
467
417
"""Return the graph for this repository format"""
468
parents_provider = self._make_parents_provider(other_repository)
418
parents_provider = self
419
if (other_repository is not None and
420
other_repository.bzrdir.transport.base !=
421
self.bzrdir.transport.base):
422
parents_provider = graph._StackedParentsProvider(
423
[parents_provider, other_repository._make_parents_provider()])
469
424
return graph.Graph(parents_provider)
471
426
def gather_stats(self, revid=None, committers=None):
548
504
path = self.bzrdir._path_for_remote_call(self._client)
549
505
if token is None:
551
err_context = {'token': token}
552
response = self._call('Repository.lock_write', path, token,
507
response = self._client.call('Repository.lock_write', path, token)
554
508
if response[0] == 'ok':
555
509
ok, token = response
511
elif response[0] == 'LockContention':
512
raise errors.LockContention('(remote lock)')
513
elif response[0] == 'UnlockableTransport':
514
raise errors.UnlockableTransport(self.bzrdir.root_transport)
515
elif response[0] == 'LockFailed':
516
raise errors.LockFailed(response[1], response[2])
558
518
raise errors.UnexpectedSmartServerResponse(response)
560
def lock_write(self, token=None, _skip_rpc=False):
520
def lock_write(self, token=None):
561
521
if not self._lock_mode:
563
if self._lock_token is not None:
564
if token != self._lock_token:
565
raise errors.TokenMismatch(token, self._lock_token)
566
self._lock_token = token
568
self._lock_token = self._remote_lock_write(token)
522
self._lock_token = self._remote_lock_write(token)
569
523
# if self._lock_token is None, then this is something like packs or
570
524
# svn where we don't get to lock the repo, or a weave style repository
571
525
# where we cannot lock it over the wire and attempts to do so will
716
671
# FIXME: It ought to be possible to call this without immediately
717
672
# triggering _ensure_real. For now it's the easiest thing to do.
718
673
self._ensure_real()
719
real_repo = self._real_repository
720
builder = real_repo.get_commit_builder(branch, parents,
674
builder = self._real_repository.get_commit_builder(branch, parents,
721
675
config, timestamp=timestamp, timezone=timezone,
722
676
committer=committer, revprops=revprops, revision_id=revision_id)
725
def add_fallback_repository(self, repository):
726
"""Add a repository to use for looking up data not held locally.
728
:param repository: A repository.
730
# XXX: At the moment the RemoteRepository will allow fallbacks
731
# unconditionally - however, a _real_repository will usually exist,
732
# and may raise an error if it's not accommodated by the underlying
733
# format. Eventually we should check when opening the repository
734
# whether it's willing to allow them or not.
736
# We need to accumulate additional repositories here, to pass them in
738
self._fallback_repositories.append(repository)
739
# They are also seen by the fallback repository. If it doesn't exist
740
# yet they'll be added then. This implicitly copies them.
743
679
def add_inventory(self, revid, inv, parents):
744
680
self._ensure_real()
745
681
return self._real_repository.add_inventory(revid, inv, parents)
811
749
not revision.is_null(revision_id)):
812
750
self.get_revision(revision_id)
814
inter = repository.InterRepository.get(source, self)
816
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
817
except NotImplementedError:
818
raise errors.IncompatibleRepositories(source, self)
753
return self._real_repository.fetch(
754
source, revision_id=revision_id, pb=pb)
820
756
def create_bundle(self, target, base, fileobj, format=None):
821
757
self._ensure_real()
822
758
self._real_repository.create_bundle(target, base, fileobj, format)
761
def control_weaves(self):
763
return self._real_repository.control_weaves
825
766
def get_ancestry(self, revision_id, topo_sorted=True):
826
767
self._ensure_real()
827
768
return self._real_repository.get_ancestry(revision_id, topo_sorted)
771
def get_inventory_weave(self):
773
return self._real_repository.get_inventory_weave()
829
775
def fileids_altered_by_revision_ids(self, revision_ids):
830
776
self._ensure_real()
831
777
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
841
787
self._ensure_real()
842
788
return self._real_repository.iter_files_bytes(desired_files)
845
def _fetch_order(self):
846
"""Decorate the real repository for now.
848
In the long term getting this back from the remote repository as part
849
of open would be more efficient.
852
return self._real_repository._fetch_order
855
def _fetch_uses_deltas(self):
856
"""Decorate the real repository for now.
858
In the long term getting this back from the remote repository as part
859
of open would be more efficient.
862
return self._real_repository._fetch_uses_deltas
865
def _fetch_reconcile(self):
866
"""Decorate the real repository for now.
868
In the long term getting this back from the remote repository as part
869
of open would be more efficient.
872
return self._real_repository._fetch_reconcile
874
def get_parent_map(self, revision_ids):
790
def get_parent_map(self, keys):
875
791
"""See bzrlib.Graph.get_parent_map()."""
876
return self._make_parents_provider().get_parent_map(revision_ids)
878
def _get_parent_map_rpc(self, keys):
792
# Hack to build up the caching logic.
793
ancestry = self._parents_map
795
# Repository is not locked, so there's no cache.
796
missing_revisions = set(keys)
799
missing_revisions = set(key for key in keys if key not in ancestry)
800
if missing_revisions:
801
parent_map = self._get_parent_map(missing_revisions)
802
if 'hpss' in debug.debug_flags:
803
mutter('retransmitted revisions: %d of %d',
804
len(set(ancestry).intersection(parent_map)),
806
ancestry.update(parent_map)
807
present_keys = [k for k in keys if k in ancestry]
808
if 'hpss' in debug.debug_flags:
809
self._requested_parents.update(present_keys)
810
mutter('Current RemoteRepository graph hit rate: %d%%',
811
100.0 * len(self._requested_parents) / len(ancestry))
812
return dict((k, ancestry[k]) for k in present_keys)
814
def _response_is_unknown_method(self, response, verb):
815
"""Return True if response is an unknonwn method response to verb.
817
:param response: The response from a smart client call_expecting_body
819
:param verb: The verb used in that call.
820
:return: True if an unknown method was encountered.
822
# This might live better on
823
# bzrlib.smart.protocol.SmartClientRequestProtocolOne
824
if (response[0] == ('error', "Generic bzr smart protocol error: "
825
"bad request '%s'" % verb) or
826
response[0] == ('error', "Generic bzr smart protocol error: "
827
"bad request u'%s'" % verb)):
828
response[1].cancel_read_body()
832
def _get_parent_map(self, keys):
879
833
"""Helper for get_parent_map that performs the RPC."""
880
medium = self._client._medium
881
if medium._is_remote_before((1, 2)):
834
medium = self._client.get_smart_medium()
835
if not medium._remote_is_at_least_1_2:
882
836
# We already found out that the server can't understand
883
837
# Repository.get_parent_map requests, so just fetch the whole
885
# XXX: Note that this will issue a deprecation warning. This is ok
886
# :- its because we're working with a deprecated server anyway, and
887
# the user will almost certainly have seen a warning about the
888
# server version already.
889
rg = self.get_revision_graph()
890
# There is an api discrepency between get_parent_map and
891
# get_revision_graph. Specifically, a "key:()" pair in
892
# get_revision_graph just means a node has no parents. For
893
# "get_parent_map" it means the node is a ghost. So fix up the
894
# graph to correct this.
895
# https://bugs.launchpad.net/bzr/+bug/214894
896
# There is one other "bug" which is that ghosts in
897
# get_revision_graph() are not returned at all. But we won't worry
898
# about that for now.
899
for node_id, parent_ids in rg.iteritems():
901
rg[node_id] = (NULL_REVISION,)
902
rg[NULL_REVISION] = ()
839
return self.get_revision_graph()
907
raise ValueError('get_parent_map(None) is not valid')
908
842
if NULL_REVISION in keys:
909
843
keys.discard(NULL_REVISION)
910
844
found_parents = {NULL_REVISION:()}
938
872
body = self._serialise_search_recipe(recipe)
939
873
path = self.bzrdir._path_for_remote_call(self._client)
941
if type(key) is not str:
943
"key %r not a plain string" % (key,))
875
assert type(key) is str
944
876
verb = 'Repository.get_parent_map'
945
877
args = (path,) + tuple(keys)
947
response = self._call_with_body_bytes_expecting_body(
949
except errors.UnknownSmartMethod:
878
response = self._client.call_with_body_bytes_expecting_body(
879
verb, args, self._serialise_search_recipe(recipe))
880
if self._response_is_unknown_method(response, verb):
950
881
# Server does not support this method, so get the whole graph.
951
882
# Worse, we have to force a disconnection, because the server now
952
883
# doesn't realise it has a body on the wire to consume, so the
957
888
medium.disconnect()
958
889
# To avoid having to disconnect repeatedly, we keep track of the
959
890
# fact the server doesn't understand remote methods added in 1.2.
960
medium._remember_remote_is_before((1, 2))
961
return self.get_revision_graph(None)
962
response_tuple, response_handler = response
963
if response_tuple[0] not in ['ok']:
964
response_handler.cancel_read_body()
965
raise errors.UnexpectedSmartServerResponse(response_tuple)
966
if response_tuple[0] == 'ok':
967
coded = bz2.decompress(response_handler.read_body_bytes())
891
medium._remote_is_at_least_1_2 = False
892
return self.get_revision_graph()
893
elif response[0][0] not in ['ok']:
894
reponse[1].cancel_read_body()
895
raise errors.UnexpectedSmartServerResponse(response[0])
896
if response[0][0] == 'ok':
897
coded = bz2.decompress(response[1].read_body_bytes())
969
899
# no revisions found
1085
1006
self._ensure_real()
1086
1007
return self._real_repository.pack()
1089
def revisions(self):
1090
"""Decorate the real repository for now.
1092
In the short term this should become a real object to intercept graph
1095
In the long term a full blown network facility is needed.
1098
return self._real_repository.revisions
1100
1009
def set_make_working_trees(self, new_value):
1102
self._real_repository.set_make_working_trees(new_value)
1105
def signatures(self):
1106
"""Decorate the real repository for now.
1108
In the long term a full blown network facility is needed to avoid
1109
creating a real repository object locally.
1112
return self._real_repository.signatures
1010
raise NotImplementedError(self.set_make_working_trees)
1114
1012
@needs_write_lock
1115
1013
def sign_revision(self, revision_id, gpg_strategy):
1116
1014
self._ensure_real()
1117
1015
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1121
"""Decorate the real repository for now.
1123
In the long term a full blown network facility is needed to avoid
1124
creating a real repository object locally.
1127
return self._real_repository.texts
1129
1017
@needs_read_lock
1130
1018
def get_revisions(self, revision_ids):
1131
1019
self._ensure_real()
1157
1045
self._ensure_real()
1158
1046
return self._real_repository.has_signature_for_revision_id(revision_id)
1048
def get_data_stream_for_search(self, search):
1049
medium = self._client.get_smart_medium()
1050
if not medium._remote_is_at_least_1_2:
1052
return self._real_repository.get_data_stream_for_search(search)
1053
REQUEST_NAME = 'Repository.stream_revisions_chunked'
1054
path = self.bzrdir._path_for_remote_call(self._client)
1055
body = self._serialise_search_recipe(search.get_recipe())
1056
response, protocol = self._client.call_with_body_bytes_expecting_body(
1057
REQUEST_NAME, (path,), body)
1059
if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
1060
# Server does not support this method, so fall back to VFS.
1061
# Worse, we have to force a disconnection, because the server now
1062
# doesn't realise it has a body on the wire to consume, so the
1063
# only way to recover is to abandon the connection.
1065
'Server is too old for streaming pull, reconnecting. '
1066
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1068
# To avoid having to disconnect repeatedly, we keep track of the
1069
# fact the server doesn't understand this remote method.
1070
medium._remote_is_at_least_1_2 = False
1072
return self._real_repository.get_data_stream_for_search(search)
1074
if response == ('ok',):
1075
return self._deserialise_stream(protocol)
1076
if response == ('NoSuchRevision', ):
1077
# We cannot easily identify the revision that is missing in this
1078
# situation without doing much more network IO. For now, bail.
1079
raise NoSuchRevision(self, "unknown")
1081
raise errors.UnexpectedSmartServerResponse(response)
1083
def _deserialise_stream(self, protocol):
1084
stream = protocol.read_streamed_body()
1085
container_parser = ContainerPushParser()
1086
for bytes in stream:
1087
container_parser.accept_bytes(bytes)
1088
records = container_parser.read_pending_records()
1089
for record_names, record_bytes in records:
1090
if len(record_names) != 1:
1091
# These records should have only one name, and that name
1092
# should be a one-element tuple.
1093
raise errors.SmartProtocolError(
1094
'Repository data stream had invalid record name %r'
1096
name_tuple = record_names[0]
1097
yield name_tuple, record_bytes
1099
def insert_data_stream(self, stream):
1101
self._real_repository.insert_data_stream(stream)
1160
1103
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1161
1104
self._ensure_real()
1162
1105
return self._real_repository.item_keys_introduced_by(revision_ids,
1194
1132
count = str(recipe[2])
1195
1133
return '\n'.join((start_keys, stop_keys, count))
1198
path = self.bzrdir._path_for_remote_call(self._client)
1200
response = self._call('PackRepository.autopack', path)
1201
except errors.UnknownSmartMethod:
1203
self._real_repository._pack_collection.autopack()
1205
if self._real_repository is not None:
1206
# Reset the real repository's cache of pack names.
1207
# XXX: At some point we may be able to skip this and just rely on
1208
# the automatic retry logic to do the right thing, but for now we
1209
# err on the side of being correct rather than being optimal.
1210
self._real_repository._pack_collection.reload_pack_names()
1211
if response[0] != 'ok':
1212
raise errors.UnexpectedSmartServerResponse(response)
1215
1136
class RemoteBranchLockableFiles(LockableFiles):
1216
1137
"""A 'LockableFiles' implementation that talks to a smart server.
1231
1152
self._dir_mode = None
1232
1153
self._file_mode = None
1155
def get(self, path):
1156
"""'get' a remote path as per the LockableFiles interface.
1158
:param path: the file to 'get'. If this is 'branch.conf', we do not
1159
just retrieve a file, instead we ask the smart server to generate
1160
a configuration for us - which is retrieved as an INI file.
1162
if path == 'branch.conf':
1163
path = self.bzrdir._path_for_remote_call(self._client)
1164
response = self._client.call_expecting_body(
1165
'Branch.get_config_file', path)
1166
assert response[0][0] == 'ok', \
1167
'unexpected response code %s' % (response[0],)
1168
return StringIO(response[1].read_body_bytes())
1171
return LockableFiles.get(self, path)
1235
1174
class RemoteBranchFormat(branch.BranchFormat):
1238
super(RemoteBranchFormat, self).__init__()
1239
self._matchingbzrdir = RemoteBzrDirFormat()
1240
self._matchingbzrdir.set_branch_format(self)
1242
1176
def __eq__(self, other):
1243
1177
return (isinstance(other, RemoteBranchFormat) and
1244
1178
self.__dict__ == other.__dict__)
1305
1240
self._control_files = None
1306
1241
self._lock_mode = None
1307
1242
self._lock_token = None
1308
self._repo_lock_token = None
1309
1243
self._lock_count = 0
1310
1244
self._leave_lock = False
1311
# The base class init is not called, so we duplicate this:
1312
hooks = branch.Branch.hooks['open']
1315
self._setup_stacking()
1317
def _setup_stacking(self):
1318
# configure stacking into the remote repository, by reading it from
1321
fallback_url = self.get_stacked_on_url()
1322
except (errors.NotStacked, errors.UnstackableBranchFormat,
1323
errors.UnstackableRepositoryFormat), e:
1325
# it's relative to this branch...
1326
fallback_url = urlutils.join(self.base, fallback_url)
1327
transports = [self.bzrdir.root_transport]
1328
if self._real_branch is not None:
1329
transports.append(self._real_branch._transport)
1330
stacked_on = branch.Branch.open(fallback_url,
1331
possible_transports=transports)
1332
self.repository.add_fallback_repository(stacked_on.repository)
1334
def _get_real_transport(self):
1335
# if we try vfs access, return the real branch's vfs transport
1337
return self._real_branch._transport
1339
_transport = property(_get_real_transport)
1341
1246
def __str__(self):
1342
1247
return "%s(%s)" % (self.__class__.__name__, self.base)
1349
1254
Used before calls to self._real_branch.
1351
if self._real_branch is None:
1352
if not vfs.vfs_enabled():
1353
raise AssertionError('smart server vfs must be enabled '
1354
'to use vfs implementation')
1256
if not self._real_branch:
1257
assert vfs.vfs_enabled()
1355
1258
self.bzrdir._ensure_real()
1356
1259
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1357
if self.repository._real_repository is None:
1358
# Give the remote repository the matching real repo.
1359
real_repo = self._real_branch.repository
1360
if isinstance(real_repo, RemoteRepository):
1361
real_repo._ensure_real()
1362
real_repo = real_repo._real_repository
1363
self.repository._set_real_repository(real_repo)
1364
# Give the real branch the remote repository to let fast-pathing
1260
# Give the remote repository the matching real repo.
1261
real_repo = self._real_branch.repository
1262
if isinstance(real_repo, RemoteRepository):
1263
real_repo._ensure_real()
1264
real_repo = real_repo._real_repository
1265
self.repository._set_real_repository(real_repo)
1266
# Give the branch the remote repository to let fast-pathing happen.
1366
1267
self._real_branch.repository = self.repository
1268
# XXX: deal with _lock_mode == 'w'
1367
1269
if self._lock_mode == 'r':
1368
1270
self._real_branch.lock_read()
1369
elif self._lock_mode == 'w':
1370
self._real_branch.lock_write(token=self._lock_token)
1372
def _translate_error(self, err, **context):
1373
self.repository._translate_error(err, branch=self, **context)
1375
def _clear_cached_state(self):
1376
super(RemoteBranch, self)._clear_cached_state()
1377
if self._real_branch is not None:
1378
self._real_branch._clear_cached_state()
1380
def _clear_cached_state_of_remote_branch_only(self):
1381
"""Like _clear_cached_state, but doesn't clear the cache of
1384
This is useful when falling back to calling a method of
1385
self._real_branch that changes state. In that case the underlying
1386
branch changes, so we need to invalidate this RemoteBranch's cache of
1387
it. However, there's no need to invalidate the _real_branch's cache
1388
too, in fact doing so might harm performance.
1390
super(RemoteBranch, self)._clear_cached_state()
1393
1273
def control_files(self):
1394
1274
# Defer actually creating RemoteBranchLockableFiles until its needed,
1408
1288
self._ensure_real()
1409
1289
return self._real_branch.get_physical_lock_status()
1411
def get_stacked_on_url(self):
1412
"""Get the URL this branch is stacked against.
1414
:raises NotStacked: If the branch is not stacked.
1415
:raises UnstackableBranchFormat: If the branch does not support
1417
:raises UnstackableRepositoryFormat: If the repository does not support
1421
# there may not be a repository yet, so we can't use
1422
# self._translate_error, so we can't use self._call either.
1423
response = self._client.call('Branch.get_stacked_on_url',
1424
self._remote_path())
1425
except errors.ErrorFromSmartServer, err:
1426
# there may not be a repository yet, so we can't call through
1427
# its _translate_error
1428
_translate_error(err, branch=self)
1429
except errors.UnknownSmartMethod, err:
1431
return self._real_branch.get_stacked_on_url()
1432
if response[0] != 'ok':
1433
raise errors.UnexpectedSmartServerResponse(response)
1436
1291
def lock_read(self):
1437
self.repository.lock_read()
1438
1292
if not self._lock_mode:
1439
1293
self._lock_mode = 'r'
1440
1294
self._lock_count = 1
1450
1304
branch_token = token
1451
1305
repo_token = self.repository.lock_write()
1452
1306
self.repository.unlock()
1453
err_context = {'token': token}
1454
response = self._call(
1455
'Branch.lock_write', self._remote_path(), branch_token,
1456
repo_token or '', **err_context)
1457
if response[0] != 'ok':
1307
path = self.bzrdir._path_for_remote_call(self._client)
1308
response = self._client.call('Branch.lock_write', path, branch_token,
1310
if response[0] == 'ok':
1311
ok, branch_token, repo_token = response
1312
return branch_token, repo_token
1313
elif response[0] == 'LockContention':
1314
raise errors.LockContention('(remote lock)')
1315
elif response[0] == 'TokenMismatch':
1316
raise errors.TokenMismatch(token, '(remote token)')
1317
elif response[0] == 'UnlockableTransport':
1318
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1319
elif response[0] == 'ReadOnlyError':
1320
raise errors.ReadOnlyError(self)
1321
elif response[0] == 'LockFailed':
1322
raise errors.LockFailed(response[1], response[2])
1458
1324
raise errors.UnexpectedSmartServerResponse(response)
1459
ok, branch_token, repo_token = response
1460
return branch_token, repo_token
1462
1326
def lock_write(self, token=None):
1463
1327
if not self._lock_mode:
1464
# Lock the branch and repo in one remote call.
1465
1328
remote_tokens = self._remote_lock_write(token)
1466
1329
self._lock_token, self._repo_lock_token = remote_tokens
1467
if not self._lock_token:
1468
raise SmartProtocolError('Remote server did not return a token!')
1469
# Tell the self.repository object that it is locked.
1470
self.repository.lock_write(
1471
self._repo_lock_token, _skip_rpc=True)
1330
assert self._lock_token, 'Remote server did not return a token!'
1331
# TODO: We really, really, really don't want to call _ensure_real
1332
# here, but it's the easiest way to ensure coherency between the
1333
# state of the RemoteBranch and RemoteRepository objects and the
1334
# physical locks. If we don't materialise the real objects here,
1335
# then getting everything in the right state later is complex, so
1336
# for now we just do it the lazy way.
1337
# -- Andrew Bennetts, 2007-02-22.
1473
1339
if self._real_branch is not None:
1474
self._real_branch.lock_write(token=self._lock_token)
1340
self._real_branch.repository.lock_write(
1341
token=self._repo_lock_token)
1343
self._real_branch.lock_write(token=self._lock_token)
1345
self._real_branch.repository.unlock()
1475
1346
if token is not None:
1476
1347
self._leave_lock = True
1349
# XXX: this case seems to be unreachable; token cannot be None.
1478
1350
self._leave_lock = False
1479
1351
self._lock_mode = 'w'
1480
1352
self._lock_count = 1
1482
1354
raise errors.ReadOnlyTransaction
1484
1356
if token is not None:
1485
# A token was given to lock_write, and we're relocking, so
1486
# check that the given token actually matches the one we
1357
# A token was given to lock_write, and we're relocking, so check
1358
# that the given token actually matches the one we already have.
1488
1359
if token != self._lock_token:
1489
1360
raise errors.TokenMismatch(token, self._lock_token)
1490
1361
self._lock_count += 1
1491
# Re-lock the repository too.
1492
self.repository.lock_write(self._repo_lock_token)
1493
1362
return self._lock_token or None
1495
1364
def _unlock(self, branch_token, repo_token):
1496
err_context = {'token': str((branch_token, repo_token))}
1497
response = self._call(
1498
'Branch.unlock', self._remote_path(), branch_token,
1499
repo_token or '', **err_context)
1365
path = self.bzrdir._path_for_remote_call(self._client)
1366
response = self._client.call('Branch.unlock', path, branch_token,
1500
1368
if response == ('ok',):
1502
raise errors.UnexpectedSmartServerResponse(response)
1370
elif response[0] == 'TokenMismatch':
1371
raise errors.TokenMismatch(
1372
str((branch_token, repo_token)), '(remote tokens)')
1374
raise errors.UnexpectedSmartServerResponse(response)
1504
1376
def unlock(self):
1506
self._lock_count -= 1
1507
if not self._lock_count:
1508
self._clear_cached_state()
1509
mode = self._lock_mode
1510
self._lock_mode = None
1511
if self._real_branch is not None:
1512
if (not self._leave_lock and mode == 'w' and
1513
self._repo_lock_token):
1514
# If this RemoteBranch will remove the physical lock
1515
# for the repository, make sure the _real_branch
1516
# doesn't do it first. (Because the _real_branch's
1517
# repository is set to be the RemoteRepository.)
1518
self._real_branch.repository.leave_lock_in_place()
1519
self._real_branch.unlock()
1521
# Only write-locked branched need to make a remote method
1522
# call to perfom the unlock.
1524
if not self._lock_token:
1525
raise AssertionError('Locked, but no token!')
1526
branch_token = self._lock_token
1527
repo_token = self._repo_lock_token
1528
self._lock_token = None
1529
self._repo_lock_token = None
1530
if not self._leave_lock:
1531
self._unlock(branch_token, repo_token)
1533
self.repository.unlock()
1377
self._lock_count -= 1
1378
if not self._lock_count:
1379
self._clear_cached_state()
1380
mode = self._lock_mode
1381
self._lock_mode = None
1382
if self._real_branch is not None:
1383
if (not self._leave_lock and mode == 'w' and
1384
self._repo_lock_token):
1385
# If this RemoteBranch will remove the physical lock for the
1386
# repository, make sure the _real_branch doesn't do it
1387
# first. (Because the _real_branch's repository is set to
1388
# be the RemoteRepository.)
1389
self._real_branch.repository.leave_lock_in_place()
1390
self._real_branch.unlock()
1392
# Only write-locked branched need to make a remote method call
1393
# to perfom the unlock.
1395
assert self._lock_token, 'Locked, but no token!'
1396
branch_token = self._lock_token
1397
repo_token = self._repo_lock_token
1398
self._lock_token = None
1399
self._repo_lock_token = None
1400
if not self._leave_lock:
1401
self._unlock(branch_token, repo_token)
1535
1403
def break_lock(self):
1536
1404
self._ensure_real()
1546
1414
raise NotImplementedError(self.dont_leave_lock_in_place)
1547
1415
self._leave_lock = False
1549
def _last_revision_info(self):
1550
response = self._call('Branch.last_revision_info', self._remote_path())
1551
if response[0] != 'ok':
1552
raise SmartProtocolError('unexpected response code %s' % (response,))
1417
def last_revision_info(self):
1418
"""See Branch.last_revision_info()."""
1419
path = self.bzrdir._path_for_remote_call(self._client)
1420
response = self._client.call('Branch.last_revision_info', path)
1421
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1553
1422
revno = int(response[1])
1554
1423
last_revision = response[2]
1555
1424
return (revno, last_revision)
1557
1426
def _gen_revision_history(self):
1558
1427
"""See Branch._gen_revision_history()."""
1559
response_tuple, response_handler = self._call_expecting_body(
1560
'Branch.revision_history', self._remote_path())
1561
if response_tuple[0] != 'ok':
1562
raise errors.UnexpectedSmartServerResponse(response_tuple)
1563
result = response_handler.read_body_bytes().split('\x00')
1428
path = self.bzrdir._path_for_remote_call(self._client)
1429
response = self._client.call_expecting_body(
1430
'Branch.revision_history', path)
1431
assert response[0][0] == 'ok', ('unexpected response code %s'
1433
result = response[1].read_body_bytes().split('\x00')
1564
1434
if result == ['']:
1568
def _remote_path(self):
1569
return self.bzrdir._path_for_remote_call(self._client)
1571
def _set_last_revision_descendant(self, revision_id, other_branch,
1572
allow_diverged=False, allow_overwrite_descendant=False):
1573
err_context = {'other_branch': other_branch}
1574
response = self._call('Branch.set_last_revision_ex',
1575
self._remote_path(), self._lock_token, self._repo_lock_token,
1576
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1578
self._clear_cached_state()
1579
if len(response) != 3 and response[0] != 'ok':
1580
raise errors.UnexpectedSmartServerResponse(response)
1581
new_revno, new_revision_id = response[1:]
1582
self._last_revision_info_cache = new_revno, new_revision_id
1583
if self._real_branch is not None:
1584
cache = new_revno, new_revision_id
1585
self._real_branch._last_revision_info_cache = cache
1587
def _set_last_revision(self, revision_id):
1588
self._clear_cached_state()
1589
response = self._call('Branch.set_last_revision',
1590
self._remote_path(), self._lock_token, self._repo_lock_token,
1592
if response != ('ok',):
1593
raise errors.UnexpectedSmartServerResponse(response)
1595
1438
@needs_write_lock
1596
1439
def set_revision_history(self, rev_history):
1597
1440
# Send just the tip revision of the history; the server will generate
1598
1441
# the full history from that. If the revision doesn't exist in this
1599
1442
# branch, NoSuchRevision will be raised.
1443
path = self.bzrdir._path_for_remote_call(self._client)
1600
1444
if rev_history == []:
1601
1445
rev_id = 'null:'
1603
1447
rev_id = rev_history[-1]
1604
self._set_last_revision(rev_id)
1448
self._clear_cached_state()
1449
response = self._client.call('Branch.set_last_revision',
1450
path, self._lock_token, self._repo_lock_token, rev_id)
1451
if response[0] == 'NoSuchRevision':
1452
raise NoSuchRevision(self, rev_id)
1454
assert response == ('ok',), (
1455
'unexpected response code %r' % (response,))
1605
1456
self._cache_revision_history(rev_history)
1607
1458
def get_parent(self):
1612
1463
self._ensure_real()
1613
1464
return self._real_branch.set_parent(url)
1615
def set_stacked_on_url(self, stacked_location):
1616
"""Set the URL this branch is stacked against.
1466
def get_config(self):
1467
return RemoteBranchConfig(self)
1618
:raises UnstackableBranchFormat: If the branch does not support
1620
:raises UnstackableRepositoryFormat: If the repository does not support
1469
def sprout(self, to_bzrdir, revision_id=None):
1470
# Like Branch.sprout, except that it sprouts a branch in the default
1471
# format, because RemoteBranches can't be created at arbitrary URLs.
1472
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1473
# to_bzrdir.create_branch...
1623
1474
self._ensure_real()
1624
return self._real_branch.set_stacked_on_url(stacked_location)
1626
def sprout(self, to_bzrdir, revision_id=None):
1627
branch_format = to_bzrdir._format._branch_format
1628
if (branch_format is None or
1629
isinstance(branch_format, RemoteBranchFormat)):
1630
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1631
# implies the same thing), but RemoteBranches can't be created at
1632
# arbitrary URLs. So create a branch in the same format as
1633
# _real_branch instead.
1634
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1635
# to_bzrdir.create_branch to create a RemoteBranch after all...
1637
result = self._real_branch._format.initialize(to_bzrdir)
1638
self.copy_content_into(result, revision_id=revision_id)
1639
result.set_parent(self.bzrdir.root_transport.base)
1641
result = branch.Branch.sprout(
1642
self, to_bzrdir, revision_id=revision_id)
1475
result = self._real_branch._format.initialize(to_bzrdir)
1476
self.copy_content_into(result, revision_id=revision_id)
1477
result.set_parent(self.bzrdir.root_transport.base)
1645
1480
@needs_write_lock
1646
1481
def pull(self, source, overwrite=False, stop_revision=None,
1648
self._clear_cached_state_of_remote_branch_only()
1483
# FIXME: This asks the real branch to run the hooks, which means
1484
# they're called with the wrong target branch parameter.
1485
# The test suite specifically allows this at present but it should be
1486
# fixed. It should get a _override_hook_target branch,
1487
# as push does. -- mbp 20070405
1649
1488
self._ensure_real()
1650
return self._real_branch.pull(
1489
self._real_branch.pull(
1651
1490
source, overwrite=overwrite, stop_revision=stop_revision,
1652
_override_hook_target=self, **kwargs)
1654
1493
@needs_read_lock
1655
1494
def push(self, target, overwrite=False, stop_revision=None):
1661
1500
def is_locked(self):
1662
1501
return self._lock_count >= 1
1665
def revision_id_to_revno(self, revision_id):
1667
return self._real_branch.revision_id_to_revno(revision_id)
1670
1503
def set_last_revision_info(self, revno, revision_id):
1671
revision_id = ensure_null(revision_id)
1673
response = self._call('Branch.set_last_revision_info',
1674
self._remote_path(), self._lock_token, self._repo_lock_token,
1675
str(revno), revision_id)
1676
except errors.UnknownSmartMethod:
1678
self._clear_cached_state_of_remote_branch_only()
1679
self._real_branch.set_last_revision_info(revno, revision_id)
1680
self._last_revision_info_cache = revno, revision_id
1682
if response == ('ok',):
1683
self._clear_cached_state()
1684
self._last_revision_info_cache = revno, revision_id
1685
# Update the _real_branch's cache too.
1686
if self._real_branch is not None:
1687
cache = self._last_revision_info_cache
1688
self._real_branch._last_revision_info_cache = cache
1690
raise errors.UnexpectedSmartServerResponse(response)
1505
self._clear_cached_state()
1506
return self._real_branch.set_last_revision_info(revno, revision_id)
1693
1508
def generate_revision_history(self, revision_id, last_rev=None,
1694
1509
other_branch=None):
1695
medium = self._client._medium
1696
if not medium._is_remote_before((1, 6)):
1698
self._set_last_revision_descendant(revision_id, other_branch,
1699
allow_diverged=True, allow_overwrite_descendant=True)
1701
except errors.UnknownSmartMethod:
1702
medium._remember_remote_is_before((1, 6))
1703
self._clear_cached_state_of_remote_branch_only()
1704
1510
self._ensure_real()
1705
self._real_branch.generate_revision_history(
1511
return self._real_branch.generate_revision_history(
1706
1512
revision_id, last_rev=last_rev, other_branch=other_branch)
1714
1520
self._ensure_real()
1715
1521
return self._real_branch.set_push_location(location)
1718
def update_revisions(self, other, stop_revision=None, overwrite=False,
1720
"""See Branch.update_revisions."""
1723
if stop_revision is None:
1724
stop_revision = other.last_revision()
1725
if revision.is_null(stop_revision):
1726
# if there are no commits, we're done.
1728
self.fetch(other, stop_revision)
1731
# Just unconditionally set the new revision. We don't care if
1732
# the branches have diverged.
1733
self._set_last_revision(stop_revision)
1735
medium = self._client._medium
1736
if not medium._is_remote_before((1, 6)):
1738
self._set_last_revision_descendant(stop_revision, other)
1740
except errors.UnknownSmartMethod:
1741
medium._remember_remote_is_before((1, 6))
1742
# Fallback for pre-1.6 servers: check for divergence
1743
# client-side, then do _set_last_revision.
1744
last_rev = revision.ensure_null(self.last_revision())
1746
graph = self.repository.get_graph()
1747
if self._check_if_descendant_or_diverged(
1748
stop_revision, last_rev, graph, other):
1749
# stop_revision is a descendant of last_rev, but we aren't
1750
# overwriting, so we're done.
1752
self._set_last_revision(stop_revision)
1523
def update_revisions(self, other, stop_revision=None, overwrite=False):
1525
return self._real_branch.update_revisions(
1526
other, stop_revision=stop_revision, overwrite=overwrite)
1529
class RemoteBranchConfig(BranchConfig):
1532
self.branch._ensure_real()
1533
return self.branch._real_branch.get_config().username()
1535
def _get_branch_data_config(self):
1536
self.branch._ensure_real()
1537
if self._branch_data_config is None:
1538
self._branch_data_config = TreeConfig(self.branch._real_branch)
1539
return self._branch_data_config
1757
1542
def _extract_tar(tar, to_dir):
1762
1547
for tarinfo in tar:
1763
1548
tar.extract(tarinfo, to_dir)
1766
def _translate_error(err, **context):
1767
"""Translate an ErrorFromSmartServer into a more useful error.
1769
Possible context keys:
1777
If the error from the server doesn't match a known pattern, then
1778
UnknownErrorFromSmartServer is raised.
1782
return context[name]
1783
except KeyError, key_err:
1784
mutter('Missing key %r in context %r', key_err.args[0], context)
1787
"""Get the path from the context if present, otherwise use first error
1791
return context['path']
1792
except KeyError, key_err:
1794
return err.error_args[0]
1795
except IndexError, idx_err:
1797
'Missing key %r in context %r', key_err.args[0], context)
1800
if err.error_verb == 'NoSuchRevision':
1801
raise NoSuchRevision(find('branch'), err.error_args[0])
1802
elif err.error_verb == 'nosuchrevision':
1803
raise NoSuchRevision(find('repository'), err.error_args[0])
1804
elif err.error_tuple == ('nobranch',):
1805
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1806
elif err.error_verb == 'norepository':
1807
raise errors.NoRepositoryPresent(find('bzrdir'))
1808
elif err.error_verb == 'LockContention':
1809
raise errors.LockContention('(remote lock)')
1810
elif err.error_verb == 'UnlockableTransport':
1811
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1812
elif err.error_verb == 'LockFailed':
1813
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1814
elif err.error_verb == 'TokenMismatch':
1815
raise errors.TokenMismatch(find('token'), '(remote token)')
1816
elif err.error_verb == 'Diverged':
1817
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1818
elif err.error_verb == 'TipChangeRejected':
1819
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1820
elif err.error_verb == 'UnstackableBranchFormat':
1821
raise errors.UnstackableBranchFormat(*err.error_args)
1822
elif err.error_verb == 'UnstackableRepositoryFormat':
1823
raise errors.UnstackableRepositoryFormat(*err.error_args)
1824
elif err.error_verb == 'NotStacked':
1825
raise errors.NotStacked(branch=find('branch'))
1826
elif err.error_verb == 'PermissionDenied':
1828
if len(err.error_args) >= 2:
1829
extra = err.error_args[1]
1832
raise errors.PermissionDenied(path, extra=extra)
1833
elif err.error_verb == 'ReadError':
1835
raise errors.ReadError(path)
1836
elif err.error_verb == 'NoSuchFile':
1838
raise errors.NoSuchFile(path)
1839
elif err.error_verb == 'FileExists':
1840
raise errors.FileExists(err.error_args[0])
1841
elif err.error_verb == 'DirectoryNotEmpty':
1842
raise errors.DirectoryNotEmpty(err.error_args[0])
1843
elif err.error_verb == 'ShortReadvError':
1844
args = err.error_args
1845
raise errors.ShortReadvError(
1846
args[0], int(args[1]), int(args[2]), int(args[3]))
1847
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1848
encoding = str(err.error_args[0]) # encoding must always be a string
1849
val = err.error_args[1]
1850
start = int(err.error_args[2])
1851
end = int(err.error_args[3])
1852
reason = str(err.error_args[4]) # reason must always be a string
1853
if val.startswith('u:'):
1854
val = val[2:].decode('utf-8')
1855
elif val.startswith('s:'):
1856
val = val[2:].decode('base64')
1857
if err.error_verb == 'UnicodeDecodeError':
1858
raise UnicodeDecodeError(encoding, val, start, end, reason)
1859
elif err.error_verb == 'UnicodeEncodeError':
1860
raise UnicodeEncodeError(encoding, val, start, end, reason)
1861
elif err.error_verb == 'ReadOnlyError':
1862
raise errors.TransportNotPossible('readonly transport')
1863
raise errors.UnknownErrorFromSmartServer(err)