90
# Note: RemoteBzrDirFormat is in bzrdir.py
92
class RemoteBzrDir(BzrDir, _RpcHelper):
113
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.remote
114
# does not have to be imported unless a remote format is involved.
116
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
117
"""Format representing bzrdirs accessed via a smart server"""
119
supports_workingtrees = False
121
colocated_branches = False
124
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
125
# XXX: It's a bit ugly that the network name is here, because we'd
126
# like to believe that format objects are stateless or at least
127
# immutable, However, we do at least avoid mutating the name after
128
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
129
self._network_name = None
132
return "%s(_network_name=%r)" % (self.__class__.__name__,
135
def get_format_description(self):
136
if self._network_name:
138
real_format = controldir.network_format_registry.get(
143
return 'Remote: ' + real_format.get_format_description()
144
return 'bzr remote bzrdir'
146
def get_format_string(self):
147
raise NotImplementedError(self.get_format_string)
149
def network_name(self):
150
if self._network_name:
151
return self._network_name
153
raise AssertionError("No network name set.")
155
def initialize_on_transport(self, transport):
157
# hand off the request to the smart server
158
client_medium = transport.get_smart_medium()
159
except errors.NoSmartMedium:
160
# TODO: lookup the local format from a server hint.
161
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
162
return local_dir_format.initialize_on_transport(transport)
163
client = _SmartClient(client_medium)
164
path = client.remote_path_from_transport(transport)
166
response = client.call('BzrDirFormat.initialize', path)
167
except errors.ErrorFromSmartServer as err:
168
_translate_error(err, path=path)
169
if response[0] != 'ok':
170
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
171
format = RemoteBzrDirFormat()
172
self._supply_sub_formats_to(format)
173
return RemoteBzrDir(transport, format)
175
def parse_NoneTrueFalse(self, arg):
182
raise AssertionError("invalid arg %r" % arg)
184
def _serialize_NoneTrueFalse(self, arg):
191
def _serialize_NoneString(self, arg):
194
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
195
create_prefix=False, force_new_repo=False, stacked_on=None,
196
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
199
# hand off the request to the smart server
200
client_medium = transport.get_smart_medium()
201
except errors.NoSmartMedium:
204
# Decline to open it if the server doesn't support our required
205
# version (3) so that the VFS-based transport will do it.
206
if client_medium.should_probe():
208
server_version = client_medium.protocol_version()
209
if server_version != '2':
213
except errors.SmartProtocolError:
214
# Apparently there's no usable smart server there, even though
215
# the medium supports the smart protocol.
220
client = _SmartClient(client_medium)
221
path = client.remote_path_from_transport(transport)
222
if client_medium._is_remote_before((1, 16)):
225
# TODO: lookup the local format from a server hint.
226
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
227
self._supply_sub_formats_to(local_dir_format)
228
return local_dir_format.initialize_on_transport_ex(transport,
229
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
230
force_new_repo=force_new_repo, stacked_on=stacked_on,
231
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
232
make_working_trees=make_working_trees, shared_repo=shared_repo,
234
return self._initialize_on_transport_ex_rpc(client, path, transport,
235
use_existing_dir, create_prefix, force_new_repo, stacked_on,
236
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
238
def _initialize_on_transport_ex_rpc(self, client, path, transport,
239
use_existing_dir, create_prefix, force_new_repo, stacked_on,
240
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
242
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
243
args.append(self._serialize_NoneTrueFalse(create_prefix))
244
args.append(self._serialize_NoneTrueFalse(force_new_repo))
245
args.append(self._serialize_NoneString(stacked_on))
246
# stack_on_pwd is often/usually our transport
249
stack_on_pwd = transport.relpath(stack_on_pwd)
252
except errors.PathNotChild:
254
args.append(self._serialize_NoneString(stack_on_pwd))
255
args.append(self._serialize_NoneString(repo_format_name))
256
args.append(self._serialize_NoneTrueFalse(make_working_trees))
257
args.append(self._serialize_NoneTrueFalse(shared_repo))
258
request_network_name = self._network_name or \
259
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
261
response = client.call('BzrDirFormat.initialize_ex_1.16',
262
request_network_name, path, *args)
263
except errors.UnknownSmartMethod:
264
client._medium._remember_remote_is_before((1,16))
265
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
266
self._supply_sub_formats_to(local_dir_format)
267
return local_dir_format.initialize_on_transport_ex(transport,
268
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
269
force_new_repo=force_new_repo, stacked_on=stacked_on,
270
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
271
make_working_trees=make_working_trees, shared_repo=shared_repo,
273
except errors.ErrorFromSmartServer as err:
274
_translate_error(err, path=path)
275
repo_path = response[0]
276
bzrdir_name = response[6]
277
require_stacking = response[7]
278
require_stacking = self.parse_NoneTrueFalse(require_stacking)
279
format = RemoteBzrDirFormat()
280
format._network_name = bzrdir_name
281
self._supply_sub_formats_to(format)
282
bzrdir = RemoteBzrDir(transport, format, _client=client)
284
repo_format = response_tuple_to_repo_format(response[1:])
288
repo_bzrdir_format = RemoteBzrDirFormat()
289
repo_bzrdir_format._network_name = response[5]
290
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
294
final_stack = response[8] or None
295
final_stack_pwd = response[9] or None
297
final_stack_pwd = urlutils.join(
298
transport.base, final_stack_pwd)
299
remote_repo = RemoteRepository(repo_bzr, repo_format)
300
if len(response) > 10:
301
# Updated server verb that locks remotely.
302
repo_lock_token = response[10] or None
303
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
305
remote_repo.dont_leave_lock_in_place()
307
remote_repo.lock_write()
308
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
309
final_stack_pwd, require_stacking)
310
policy.acquire_repository()
314
bzrdir._format.set_branch_format(self.get_branch_format())
316
# The repo has already been created, but we need to make sure that
317
# we'll make a stackable branch.
318
bzrdir._format.require_stacking(_skip_repo=True)
319
return remote_repo, bzrdir, require_stacking, policy
321
def _open(self, transport):
322
return RemoteBzrDir(transport, self)
324
def __eq__(self, other):
325
if not isinstance(other, RemoteBzrDirFormat):
327
return self.get_format_description() == other.get_format_description()
329
def __return_repository_format(self):
330
# Always return a RemoteRepositoryFormat object, but if a specific bzr
331
# repository format has been asked for, tell the RemoteRepositoryFormat
332
# that it should use that for init() etc.
333
result = RemoteRepositoryFormat()
334
custom_format = getattr(self, '_repository_format', None)
336
if isinstance(custom_format, RemoteRepositoryFormat):
339
# We will use the custom format to create repositories over the
340
# wire; expose its details like rich_root_data for code to
342
result._custom_format = custom_format
345
def get_branch_format(self):
346
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
347
if not isinstance(result, RemoteBranchFormat):
348
new_result = RemoteBranchFormat()
349
new_result._custom_format = result
351
self.set_branch_format(new_result)
355
repository_format = property(__return_repository_format,
356
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
359
class RemoteControlStore(_mod_config.IniFileStore):
360
"""Control store which attempts to use HPSS calls to retrieve control store.
362
Note that this is specific to bzr-based formats.
365
def __init__(self, bzrdir):
366
super(RemoteControlStore, self).__init__()
367
self.controldir = bzrdir
368
self._real_store = None
370
def lock_write(self, token=None):
372
return self._real_store.lock_write(token)
376
return self._real_store.unlock()
380
# We need to be able to override the undecorated implementation
381
self.save_without_locking()
383
def save_without_locking(self):
384
super(RemoteControlStore, self).save()
386
def _ensure_real(self):
387
self.controldir._ensure_real()
388
if self._real_store is None:
389
self._real_store = _mod_config.ControlStore(self.controldir)
391
def external_url(self):
392
return urlutils.join(self.branch.user_url, 'control.conf')
394
def _load_content(self):
395
medium = self.controldir._client._medium
396
path = self.controldir._path_for_remote_call(self.controldir._client)
398
response, handler = self.controldir._call_expecting_body(
399
'BzrDir.get_config_file', path)
400
except errors.UnknownSmartMethod:
402
return self._real_store._load_content()
403
if len(response) and response[0] != 'ok':
404
raise errors.UnexpectedSmartServerResponse(response)
405
return handler.read_body_bytes()
407
def _save_content(self, content):
408
# FIXME JRV 2011-11-22: Ideally this should use a
409
# HPSS call too, but at the moment it is not possible
410
# to write lock control directories.
412
return self._real_store._save_content(content)
415
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
93
416
"""Control directory on a remote server, accessed via bzr:// or similar."""
95
418
def __init__(self, transport, format, _client=None, _force_probe=False):
326
765
"""See BzrDir._get_tree_branch()."""
327
766
return None, self.open_branch(name=name)
329
def open_branch(self, name=None, unsupported=False,
330
ignore_fallbacks=False):
332
raise NotImplementedError('unsupported flag support not implemented yet.')
333
if self._next_open_branch_result is not None:
334
# See create_branch for details.
335
result = self._next_open_branch_result
336
self._next_open_branch_result = None
338
response = self._get_branch_reference()
339
if response[0] == 'ref':
768
def _open_branch(self, name, kind, location_or_format,
769
ignore_fallbacks=False, possible_transports=None):
340
771
# a branch reference, use the existing BranchReference logic.
341
772
format = BranchReferenceFormat()
342
773
return format.open(self, name=name, _found=True,
343
location=response[1], ignore_fallbacks=ignore_fallbacks)
344
branch_format_name = response[1]
774
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
775
possible_transports=possible_transports)
776
branch_format_name = location_or_format
345
777
if not branch_format_name:
346
778
branch_format_name = None
347
779
format = RemoteBranchFormat(network_name=branch_format_name)
348
780
return RemoteBranch(self, self.find_repository(), format=format,
349
setup_stacking=not ignore_fallbacks, name=name)
781
setup_stacking=not ignore_fallbacks, name=name,
782
possible_transports=possible_transports)
784
def open_branch(self, name=None, unsupported=False,
785
ignore_fallbacks=False, possible_transports=None):
787
name = self._get_selected_branch()
789
raise errors.NoColocatedBranchSupport(self)
791
raise NotImplementedError('unsupported flag support not implemented yet.')
792
if self._next_open_branch_result is not None:
793
# See create_branch for details.
794
result = self._next_open_branch_result
795
self._next_open_branch_result = None
797
response = self._get_branch_reference()
798
return self._open_branch(name, response[0], response[1],
799
possible_transports=possible_transports,
800
ignore_fallbacks=ignore_fallbacks)
351
802
def _open_repo_v1(self, path):
352
803
verb = 'BzrDir.find_repository'
533
1008
self._custom_format.supports_tree_reference
534
1009
return self._supports_tree_reference
536
def _vfs_initialize(self, a_bzrdir, shared):
1012
def revision_graph_can_have_wrong_parents(self):
1013
if self._revision_graph_can_have_wrong_parents is None:
1015
self._revision_graph_can_have_wrong_parents = \
1016
self._custom_format.revision_graph_can_have_wrong_parents
1017
return self._revision_graph_can_have_wrong_parents
1019
def _vfs_initialize(self, a_controldir, shared):
537
1020
"""Helper for common code in initialize."""
538
1021
if self._custom_format:
539
1022
# Custom format requested
540
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1023
result = self._custom_format.initialize(a_controldir, shared=shared)
541
1024
elif self._creating_bzrdir is not None:
542
1025
# Use the format that the repository we were created to back
544
1027
prior_repo = self._creating_bzrdir.open_repository()
545
1028
prior_repo._ensure_real()
546
1029
result = prior_repo._real_repository._format.initialize(
547
a_bzrdir, shared=shared)
1030
a_controldir, shared=shared)
549
1032
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
550
1033
# support remote initialization.
551
1034
# We delegate to a real object at this point (as RemoteBzrDir
552
1035
# delegate to the repository format which would lead to infinite
553
# recursion if we just called a_bzrdir.create_repository.
554
a_bzrdir._ensure_real()
555
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1036
# recursion if we just called a_controldir.create_repository.
1037
a_controldir._ensure_real()
1038
result = a_controldir._real_bzrdir.create_repository(shared=shared)
556
1039
if not isinstance(result, RemoteRepository):
557
return self.open(a_bzrdir)
1040
return self.open(a_controldir)
561
def initialize(self, a_bzrdir, shared=False):
1044
def initialize(self, a_controldir, shared=False):
562
1045
# Being asked to create on a non RemoteBzrDir:
563
if not isinstance(a_bzrdir, RemoteBzrDir):
564
return self._vfs_initialize(a_bzrdir, shared)
565
medium = a_bzrdir._client._medium
1046
if not isinstance(a_controldir, RemoteBzrDir):
1047
return self._vfs_initialize(a_controldir, shared)
1048
medium = a_controldir._client._medium
566
1049
if medium._is_remote_before((1, 13)):
567
return self._vfs_initialize(a_bzrdir, shared)
1050
return self._vfs_initialize(a_controldir, shared)
568
1051
# Creating on a remote bzr dir.
569
1052
# 1) get the network name to use.
570
1053
if self._custom_format:
572
1055
elif self._network_name:
573
1056
network_name = self._network_name
575
# Select the current bzrlib default and ask for that.
576
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1058
# Select the current breezy default and ask for that.
1059
reference_bzrdir_format = controldir.format_registry.get('default')()
577
1060
reference_format = reference_bzrdir_format.repository_format
578
1061
network_name = reference_format.network_name()
579
1062
# 2) try direct creation via RPC
580
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1063
path = a_controldir._path_for_remote_call(a_controldir._client)
581
1064
verb = 'BzrDir.create_repository'
583
1066
shared_str = 'True'
585
1068
shared_str = 'False'
587
response = a_bzrdir._call(verb, path, network_name, shared_str)
1070
response = a_controldir._call(verb, path, network_name, shared_str)
588
1071
except errors.UnknownSmartMethod:
589
1072
# Fallback - use vfs methods
590
1073
medium._remember_remote_is_before((1, 13))
591
return self._vfs_initialize(a_bzrdir, shared)
1074
return self._vfs_initialize(a_controldir, shared)
593
1076
# Turn the response into a RemoteRepository object.
594
1077
format = response_tuple_to_repo_format(response[1:])
595
1078
# Used to support creating a real format instance when needed.
596
format._creating_bzrdir = a_bzrdir
597
remote_repo = RemoteRepository(a_bzrdir, format)
1079
format._creating_bzrdir = a_controldir
1080
remote_repo = RemoteRepository(a_controldir, format)
598
1081
format._creating_repo = remote_repo
599
1082
return remote_repo
601
def open(self, a_bzrdir):
602
if not isinstance(a_bzrdir, RemoteBzrDir):
603
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
604
return a_bzrdir.open_repository()
1084
def open(self, a_controldir):
1085
if not isinstance(a_controldir, RemoteBzrDir):
1086
raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1087
return a_controldir.open_repository()
606
1089
def _ensure_real(self):
607
1090
if self._custom_format is None:
608
self._custom_format = repository.network_format_registry.get(
1092
self._custom_format = _mod_repository.network_format_registry.get(
1095
raise errors.UnknownFormatError(kind='repository',
1096
format=self._network_name)
612
1099
def _fetch_order(self):
1204
1773
raise errors.UnexpectedSmartServerResponse(response)
1206
1776
def sprout(self, to_bzrdir, revision_id=None):
1207
# TODO: Option to control what format is created?
1209
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1777
"""Create a descendent repository for new development.
1779
Unlike clone, this does not copy the settings of the repository.
1781
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1211
1782
dest_repo.fetch(self, revision_id=revision_id)
1212
1783
return dest_repo
1785
def _create_sprouting_repo(self, a_controldir, shared):
1786
if not isinstance(a_controldir._format, self.controldir._format.__class__):
1787
# use target default format.
1788
dest_repo = a_controldir.create_repository()
1790
# Most control formats need the repository to be specifically
1791
# created, but on some old all-in-one formats it's not needed
1793
dest_repo = self._format.initialize(a_controldir, shared=shared)
1794
except errors.UninitializableFormat:
1795
dest_repo = a_controldir.open_repository()
1214
1798
### These methods are just thin shims to the VFS object for now.
1216
1801
def revision_tree(self, revision_id):
1218
return self._real_repository.revision_tree(revision_id)
1802
revision_id = _mod_revision.ensure_null(revision_id)
1803
if revision_id == _mod_revision.NULL_REVISION:
1804
return InventoryRevisionTree(self,
1805
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1807
return list(self.revision_trees([revision_id]))[0]
1220
1809
def get_serializer_format(self):
1222
return self._real_repository.get_serializer_format()
1810
path = self.controldir._path_for_remote_call(self._client)
1812
response = self._call('VersionedFileRepository.get_serializer_format',
1814
except errors.UnknownSmartMethod:
1816
return self._real_repository.get_serializer_format()
1817
if response[0] != 'ok':
1818
raise errors.UnexpectedSmartServerResponse(response)
1224
1821
def get_commit_builder(self, branch, parents, config, timestamp=None,
1225
1822
timezone=None, committer=None, revprops=None,
1227
# FIXME: It ought to be possible to call this without immediately
1228
# triggering _ensure_real. For now it's the easiest thing to do.
1230
real_repo = self._real_repository
1231
builder = real_repo.get_commit_builder(branch, parents,
1232
config, timestamp=timestamp, timezone=timezone,
1233
committer=committer, revprops=revprops, revision_id=revision_id)
1823
revision_id=None, lossy=False):
1824
"""Obtain a CommitBuilder for this repository.
1826
:param branch: Branch to commit to.
1827
:param parents: Revision ids of the parents of the new revision.
1828
:param config: Configuration to use.
1829
:param timestamp: Optional timestamp recorded for commit.
1830
:param timezone: Optional timezone for timestamp.
1831
:param committer: Optional committer to set for commit.
1832
:param revprops: Optional dictionary of revision properties.
1833
:param revision_id: Optional revision id.
1834
:param lossy: Whether to discard data that can not be natively
1835
represented, when pushing to a foreign VCS
1837
if self._fallback_repositories and not self._format.supports_chks:
1838
raise errors.BzrError("Cannot commit directly to a stacked branch"
1839
" in pre-2a formats. See "
1840
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1841
if self._format.rich_root_data:
1842
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1844
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1845
result = commit_builder_kls(self, parents, config,
1846
timestamp, timezone, committer, revprops, revision_id,
1848
self.start_write_group()
1236
1851
def add_fallback_repository(self, repository):
1237
1852
"""Add a repository to use for looking up data not held locally.
1281
1897
delta, new_revision_id, parents, basis_inv=basis_inv,
1282
1898
propagate_caches=propagate_caches)
1284
def add_revision(self, rev_id, rev, inv=None, config=None):
1286
return self._real_repository.add_revision(
1287
rev_id, rev, inv=inv, config=config)
1900
def add_revision(self, revision_id, rev, inv=None):
1901
_mod_revision.check_not_reserved_id(revision_id)
1902
key = (revision_id,)
1903
# check inventory present
1904
if not self.inventories.get_parent_map([key]):
1906
raise errors.WeaveRevisionNotPresent(revision_id,
1909
# yes, this is not suitable for adding with ghosts.
1910
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1913
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1914
self._add_revision(rev)
1916
def _add_revision(self, rev):
1917
if self._real_repository is not None:
1918
return self._real_repository._add_revision(rev)
1919
text = self._serializer.write_revision_to_string(rev)
1920
key = (rev.revision_id,)
1921
parents = tuple((parent,) for parent in rev.parent_ids)
1922
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1923
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1924
self._format, self._write_group_tokens)
1289
1926
@needs_read_lock
1290
1927
def get_inventory(self, revision_id):
1928
return list(self.iter_inventories([revision_id]))[0]
1930
def _iter_inventories_rpc(self, revision_ids, ordering):
1931
if ordering is None:
1932
ordering = 'unordered'
1933
path = self.controldir._path_for_remote_call(self._client)
1934
body = "\n".join(revision_ids)
1935
response_tuple, response_handler = (
1936
self._call_with_body_bytes_expecting_body(
1937
"VersionedFileRepository.get_inventories",
1938
(path, ordering), body))
1939
if response_tuple[0] != "ok":
1940
raise errors.UnexpectedSmartServerResponse(response_tuple)
1941
deserializer = inventory_delta.InventoryDeltaDeserializer()
1942
byte_stream = response_handler.read_streamed_body()
1943
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1945
# no results whatsoever
1947
src_format, stream = decoded
1948
if src_format.network_name() != self._format.network_name():
1949
raise AssertionError(
1950
"Mismatched RemoteRepository and stream src %r, %r" % (
1951
src_format.network_name(), self._format.network_name()))
1952
# ignore the src format, it's not really relevant
1953
prev_inv = Inventory(root_id=None,
1954
revision_id=_mod_revision.NULL_REVISION)
1955
# there should be just one substream, with inventory deltas
1956
substream_kind, substream = next(stream)
1957
if substream_kind != "inventory-deltas":
1958
raise AssertionError(
1959
"Unexpected stream %r received" % substream_kind)
1960
for record in substream:
1961
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1962
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1963
if parent_id != prev_inv.revision_id:
1964
raise AssertionError("invalid base %r != %r" % (parent_id,
1965
prev_inv.revision_id))
1966
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1967
yield inv, inv.revision_id
1970
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1291
1971
self._ensure_real()
1292
return self._real_repository.get_inventory(revision_id)
1972
return self._real_repository._iter_inventories(revision_ids, ordering)
1294
1974
def iter_inventories(self, revision_ids, ordering=None):
1296
return self._real_repository.iter_inventories(revision_ids, ordering)
1975
"""Get many inventories by revision_ids.
1977
This will buffer some or all of the texts used in constructing the
1978
inventories in memory, but will only parse a single inventory at a
1981
:param revision_ids: The expected revision ids of the inventories.
1982
:param ordering: optional ordering, e.g. 'topological'. If not
1983
specified, the order of revision_ids will be preserved (by
1984
buffering if necessary).
1985
:return: An iterator of inventories.
1987
if ((None in revision_ids)
1988
or (_mod_revision.NULL_REVISION in revision_ids)):
1989
raise ValueError('cannot get null revision inventory')
1990
for inv, revid in self._iter_inventories(revision_ids, ordering):
1992
raise errors.NoSuchRevision(self, revid)
1995
def _iter_inventories(self, revision_ids, ordering=None):
1996
if len(revision_ids) == 0:
1998
missing = set(revision_ids)
1999
if ordering is None:
2000
order_as_requested = True
2002
order = list(revision_ids)
2004
next_revid = order.pop()
2006
order_as_requested = False
2007
if ordering != 'unordered' and self._fallback_repositories:
2008
raise ValueError('unsupported ordering %r' % ordering)
2009
iter_inv_fns = [self._iter_inventories_rpc] + [
2010
fallback._iter_inventories for fallback in
2011
self._fallback_repositories]
2013
for iter_inv in iter_inv_fns:
2014
request = [revid for revid in revision_ids if revid in missing]
2015
for inv, revid in iter_inv(request, ordering):
2018
missing.remove(inv.revision_id)
2019
if ordering != 'unordered':
2023
if order_as_requested:
2024
# Yield as many results as we can while preserving order.
2025
while next_revid in invs:
2026
inv = invs.pop(next_revid)
2027
yield inv, inv.revision_id
2029
next_revid = order.pop()
2031
# We still want to fully consume the stream, just
2032
# in case it is not actually finished at this point
2035
except errors.UnknownSmartMethod:
2036
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2040
if order_as_requested:
2041
if next_revid is not None:
2042
yield None, next_revid
2045
yield invs.get(revid), revid
2048
yield None, missing.pop()
1298
2050
@needs_read_lock
1299
2051
def get_revision(self, revision_id):
1301
return self._real_repository.get_revision(revision_id)
2052
return self.get_revisions([revision_id])[0]
1303
2054
def get_transaction(self):
1304
2055
self._ensure_real()
1305
2056
return self._real_repository.get_transaction()
1307
2058
@needs_read_lock
1308
def clone(self, a_bzrdir, revision_id=None):
1310
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2059
def clone(self, a_controldir, revision_id=None):
2060
dest_repo = self._create_sprouting_repo(
2061
a_controldir, shared=self.is_shared())
2062
self.copy_content_into(dest_repo, revision_id)
1312
2065
def make_working_trees(self):
1313
2066
"""See Repository.make_working_trees"""
1315
return self._real_repository.make_working_trees()
2067
path = self.controldir._path_for_remote_call(self._client)
2069
response = self._call('Repository.make_working_trees', path)
2070
except errors.UnknownSmartMethod:
2072
return self._real_repository.make_working_trees()
2073
if response[0] not in ('yes', 'no'):
2074
raise SmartProtocolError('unexpected response code %s' % (response,))
2075
return response[0] == 'yes'
1317
2077
def refresh_data(self):
1318
2078
"""Re-read any data needed to synchronise with disk.
1320
2080
This method is intended to be called after another repository instance
1321
2081
(such as one used by a smart server) has inserted data into the
1322
2082
repository. On all repositories this will work outside of write groups.
1323
Some repository formats (pack and newer for bzrlib native formats)
2083
Some repository formats (pack and newer for breezy native formats)
1324
2084
support refresh_data inside write groups. If called inside a write
1325
2085
group on a repository that does not support refreshing in a write group
1326
2086
IsInWriteGroupError will be raised.
1328
2088
if self._real_repository is not None:
1329
2089
self._real_repository.refresh_data()
2090
# Refresh the parents cache for this object
2091
self._unstacked_provider.disable_cache()
2092
self._unstacked_provider.enable_cache()
1331
2094
def revision_ids_to_search_result(self, result_set):
1332
2095
"""Convert a set of revision ids to a graph SearchResult."""
1333
2096
result_parents = set()
1334
for parents in self.get_graph().get_parent_map(
1335
result_set).itervalues():
2097
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
1336
2098
result_parents.update(parents)
1337
2099
included_keys = result_set.intersection(result_parents)
1338
2100
start_keys = result_set.difference(included_keys)
1339
2101
exclude_keys = result_parents.difference(result_set)
1340
result = graph.SearchResult(start_keys, exclude_keys,
2102
result = vf_search.SearchResult(start_keys, exclude_keys,
1341
2103
len(result_set), result_set)
1344
2106
@needs_read_lock
1345
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
2107
def search_missing_revision_ids(self, other,
2108
find_ghosts=True, revision_ids=None, if_present_ids=None,
1346
2110
"""Return the revision ids that other has that this does not.
1348
2112
These are returned in topological order.
1350
2114
revision_id: only return revision ids included by revision_id.
1352
return repository.InterRepository.get(
1353
other, self).search_missing_revision_ids(revision_id, find_ghosts)
2116
inter_repo = _mod_repository.InterRepository.get(other, self)
2117
return inter_repo.search_missing_revision_ids(
2118
find_ghosts=find_ghosts, revision_ids=revision_ids,
2119
if_present_ids=if_present_ids, limit=limit)
1355
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
2121
def fetch(self, source, revision_id=None, find_ghosts=False,
1356
2122
fetch_spec=None):
1357
2123
# No base implementation to use as RemoteRepository is not a subclass
1358
2124
# of Repository; so this is a copy of Repository.fetch().
1397
2162
return self._real_repository._get_versioned_file_checker(
1398
2163
revisions, revision_versions_cache)
2165
def _iter_files_bytes_rpc(self, desired_files, absent):
2166
path = self.controldir._path_for_remote_call(self._client)
2169
for (file_id, revid, identifier) in desired_files:
2170
lines.append("%s\0%s" % (
2171
osutils.safe_file_id(file_id),
2172
osutils.safe_revision_id(revid)))
2173
identifiers.append(identifier)
2174
(response_tuple, response_handler) = (
2175
self._call_with_body_bytes_expecting_body(
2176
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2177
if response_tuple != ('ok', ):
2178
response_handler.cancel_read_body()
2179
raise errors.UnexpectedSmartServerResponse(response_tuple)
2180
byte_stream = response_handler.read_streamed_body()
2181
def decompress_stream(start, byte_stream, unused):
2182
decompressor = zlib.decompressobj()
2183
yield decompressor.decompress(start)
2184
while decompressor.unused_data == "":
2186
data = next(byte_stream)
2187
except StopIteration:
2189
yield decompressor.decompress(data)
2190
yield decompressor.flush()
2191
unused.append(decompressor.unused_data)
2194
while not "\n" in unused:
2195
unused += next(byte_stream)
2196
header, rest = unused.split("\n", 1)
2197
args = header.split("\0")
2198
if args[0] == "absent":
2199
absent[identifiers[int(args[3])]] = (args[1], args[2])
2202
elif args[0] == "ok":
2205
raise errors.UnexpectedSmartServerResponse(args)
2207
yield (identifiers[idx],
2208
decompress_stream(rest, byte_stream, unused_chunks))
2209
unused = "".join(unused_chunks)
1400
2211
def iter_files_bytes(self, desired_files):
1401
2212
"""See Repository.iter_file_bytes.
1404
return self._real_repository.iter_files_bytes(desired_files)
2216
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2217
desired_files, absent):
2218
yield identifier, bytes_iterator
2219
for fallback in self._fallback_repositories:
2222
desired_files = [(key[0], key[1], identifier)
2223
for identifier, key in viewitems(absent)]
2224
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2225
del absent[identifier]
2226
yield identifier, bytes_iterator
2228
# There may be more missing items, but raise an exception
2230
missing_identifier = next(iter(absent))
2231
missing_key = absent[missing_identifier]
2232
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2233
file_id=missing_key[0])
2234
except errors.UnknownSmartMethod:
2236
for (identifier, bytes_iterator) in (
2237
self._real_repository.iter_files_bytes(desired_files)):
2238
yield identifier, bytes_iterator
2240
def get_cached_parent_map(self, revision_ids):
2241
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2242
return self._unstacked_provider.get_cached_parent_map(revision_ids)
1406
2244
def get_parent_map(self, revision_ids):
1407
"""See bzrlib.Graph.get_parent_map()."""
2245
"""See breezy.Graph.get_parent_map()."""
1408
2246
return self._make_parents_provider().get_parent_map(revision_ids)
1410
2248
def _get_parent_map_rpc(self, keys):
1541
2368
@needs_read_lock
1542
2369
def get_signature_text(self, revision_id):
1544
return self._real_repository.get_signature_text(revision_id)
2370
path = self.controldir._path_for_remote_call(self._client)
2372
response_tuple, response_handler = self._call_expecting_body(
2373
'Repository.get_revision_signature_text', path, revision_id)
2374
except errors.UnknownSmartMethod:
2376
return self._real_repository.get_signature_text(revision_id)
2377
except errors.NoSuchRevision as err:
2378
for fallback in self._fallback_repositories:
2380
return fallback.get_signature_text(revision_id)
2381
except errors.NoSuchRevision:
2385
if response_tuple[0] != 'ok':
2386
raise errors.UnexpectedSmartServerResponse(response_tuple)
2387
return response_handler.read_body_bytes()
1546
2389
@needs_read_lock
1547
2390
def _get_inventory_xml(self, revision_id):
2391
# This call is used by older working tree formats,
2392
# which stored a serialized basis inventory.
1548
2393
self._ensure_real()
1549
2394
return self._real_repository._get_inventory_xml(revision_id)
1551
2397
def reconcile(self, other=None, thorough=False):
1553
return self._real_repository.reconcile(other=other, thorough=thorough)
2398
from .reconcile import RepoReconciler
2399
path = self.controldir._path_for_remote_call(self._client)
2401
response, handler = self._call_expecting_body(
2402
'Repository.reconcile', path, self._lock_token)
2403
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2405
return self._real_repository.reconcile(other=other, thorough=thorough)
2406
if response != ('ok', ):
2407
raise errors.UnexpectedSmartServerResponse(response)
2408
body = handler.read_body_bytes()
2409
result = RepoReconciler(self)
2410
for line in body.split('\n'):
2413
key, val_text = line.split(':')
2414
if key == "garbage_inventories":
2415
result.garbage_inventories = int(val_text)
2416
elif key == "inconsistent_parents":
2417
result.inconsistent_parents = int(val_text)
2419
mutter("unknown reconcile key %r" % key)
1555
2422
def all_revision_ids(self):
1557
return self._real_repository.all_revision_ids()
2423
path = self.controldir._path_for_remote_call(self._client)
2425
response_tuple, response_handler = self._call_expecting_body(
2426
"Repository.all_revision_ids", path)
2427
except errors.UnknownSmartMethod:
2429
return self._real_repository.all_revision_ids()
2430
if response_tuple != ("ok", ):
2431
raise errors.UnexpectedSmartServerResponse(response_tuple)
2432
revids = set(response_handler.read_body_bytes().splitlines())
2433
for fallback in self._fallback_repositories:
2434
revids.update(set(fallback.all_revision_ids()))
2437
def _filtered_revision_trees(self, revision_ids, file_ids):
2438
"""Return Tree for a revision on this branch with only some files.
2440
:param revision_ids: a sequence of revision-ids;
2441
a revision-id may not be None or 'null:'
2442
:param file_ids: if not None, the result is filtered
2443
so that only those file-ids, their parents and their
2444
children are included.
2446
inventories = self.iter_inventories(revision_ids)
2447
for inv in inventories:
2448
# Should we introduce a FilteredRevisionTree class rather
2449
# than pre-filter the inventory here?
2450
filtered_inv = inv.filter(file_ids)
2451
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
1559
2453
@needs_read_lock
1560
2454
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1562
return self._real_repository.get_deltas_for_revisions(revisions,
1563
specific_fileids=specific_fileids)
2455
medium = self._client._medium
2456
if medium._is_remote_before((1, 2)):
2458
for delta in self._real_repository.get_deltas_for_revisions(
2459
revisions, specific_fileids):
2462
# Get the revision-ids of interest
2463
required_trees = set()
2464
for revision in revisions:
2465
required_trees.add(revision.revision_id)
2466
required_trees.update(revision.parent_ids[:1])
2468
# Get the matching filtered trees. Note that it's more
2469
# efficient to pass filtered trees to changes_from() rather
2470
# than doing the filtering afterwards. changes_from() could
2471
# arguably do the filtering itself but it's path-based, not
2472
# file-id based, so filtering before or afterwards is
2474
if specific_fileids is None:
2475
trees = dict((t.get_revision_id(), t) for
2476
t in self.revision_trees(required_trees))
2478
trees = dict((t.get_revision_id(), t) for
2479
t in self._filtered_revision_trees(required_trees,
2482
# Calculate the deltas
2483
for revision in revisions:
2484
if not revision.parent_ids:
2485
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2487
old_tree = trees[revision.parent_ids[0]]
2488
yield trees[revision.revision_id].changes_from(old_tree)
1565
2490
@needs_read_lock
1566
2491
def get_revision_delta(self, revision_id, specific_fileids=None):
1568
return self._real_repository.get_revision_delta(revision_id,
1569
specific_fileids=specific_fileids)
2492
r = self.get_revision(revision_id)
2493
return list(self.get_deltas_for_revisions([r],
2494
specific_fileids=specific_fileids))[0]
1571
2496
@needs_read_lock
1572
2497
def revision_trees(self, revision_ids):
1574
return self._real_repository.revision_trees(revision_ids)
2498
inventories = self.iter_inventories(revision_ids)
2499
for inv in inventories:
2500
yield InventoryRevisionTree(self, inv, inv.revision_id)
1576
2502
@needs_read_lock
1577
2503
def get_revision_reconcile(self, revision_id):
1689
2628
self._ensure_real()
1690
2629
return self._real_repository.texts
2631
def _iter_revisions_rpc(self, revision_ids):
2632
body = "\n".join(revision_ids)
2633
path = self.controldir._path_for_remote_call(self._client)
2634
response_tuple, response_handler = (
2635
self._call_with_body_bytes_expecting_body(
2636
"Repository.iter_revisions", (path, ), body))
2637
if response_tuple[0] != "ok":
2638
raise errors.UnexpectedSmartServerResponse(response_tuple)
2639
serializer_format = response_tuple[1]
2640
serializer = serializer_format_registry.get(serializer_format)
2641
byte_stream = response_handler.read_streamed_body()
2642
decompressor = zlib.decompressobj()
2644
for bytes in byte_stream:
2645
chunks.append(decompressor.decompress(bytes))
2646
if decompressor.unused_data != "":
2647
chunks.append(decompressor.flush())
2648
yield serializer.read_revision_from_string("".join(chunks))
2649
unused = decompressor.unused_data
2650
decompressor = zlib.decompressobj()
2651
chunks = [decompressor.decompress(unused)]
2652
chunks.append(decompressor.flush())
2653
text = "".join(chunks)
2655
yield serializer.read_revision_from_string("".join(chunks))
1692
2657
@needs_read_lock
1693
2658
def get_revisions(self, revision_ids):
1695
return self._real_repository.get_revisions(revision_ids)
2659
if revision_ids is None:
2660
revision_ids = self.all_revision_ids()
2662
for rev_id in revision_ids:
2663
if not rev_id or not isinstance(rev_id, basestring):
2664
raise errors.InvalidRevisionId(
2665
revision_id=rev_id, branch=self)
2667
missing = set(revision_ids)
2669
for rev in self._iter_revisions_rpc(revision_ids):
2670
missing.remove(rev.revision_id)
2671
revs[rev.revision_id] = rev
2672
except errors.UnknownSmartMethod:
2674
return self._real_repository.get_revisions(revision_ids)
2675
for fallback in self._fallback_repositories:
2678
for revid in list(missing):
2679
# XXX JRV 2011-11-20: It would be nice if there was a
2680
# public method on Repository that could be used to query
2681
# for revision objects *without* failing completely if one
2682
# was missing. There is VersionedFileRepository._iter_revisions,
2683
# but unfortunately that's private and not provided by
2684
# all repository implementations.
2686
revs[revid] = fallback.get_revision(revid)
2687
except errors.NoSuchRevision:
2690
missing.remove(revid)
2692
raise errors.NoSuchRevision(self, list(missing)[0])
2693
return [revs[revid] for revid in revision_ids]
1697
2695
def supports_rich_root(self):
1698
2696
return self._format.rich_root_data
1700
def iter_reverse_revision_history(self, revision_id):
1702
return self._real_repository.iter_reverse_revision_history(revision_id)
1705
2699
def _serializer(self):
1706
2700
return self._format._serializer
1708
2703
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1710
return self._real_repository.store_revision_signature(
1711
gpg_strategy, plaintext, revision_id)
2704
signature = gpg_strategy.sign(plaintext)
2705
self.add_signature_text(revision_id, signature)
1713
2707
def add_signature_text(self, revision_id, signature):
1715
return self._real_repository.add_signature_text(revision_id, signature)
2708
if self._real_repository:
2709
# If there is a real repository the write group will
2710
# be in the real repository as well, so use that:
2712
return self._real_repository.add_signature_text(
2713
revision_id, signature)
2714
path = self.controldir._path_for_remote_call(self._client)
2715
response, handler = self._call_with_body_bytes_expecting_body(
2716
'Repository.add_signature_text', (path, self._lock_token,
2717
revision_id) + tuple(self._write_group_tokens), signature)
2718
handler.cancel_read_body()
2720
if response[0] != 'ok':
2721
raise errors.UnexpectedSmartServerResponse(response)
2722
self._write_group_tokens = response[1:]
1717
2724
def has_signature_for_revision_id(self, revision_id):
1719
return self._real_repository.has_signature_for_revision_id(revision_id)
2725
path = self.controldir._path_for_remote_call(self._client)
2727
response = self._call('Repository.has_signature_for_revision_id',
2729
except errors.UnknownSmartMethod:
2731
return self._real_repository.has_signature_for_revision_id(
2733
if response[0] not in ('yes', 'no'):
2734
raise SmartProtocolError('unexpected response code %s' % (response,))
2735
if response[0] == 'yes':
2737
for fallback in self._fallback_repositories:
2738
if fallback.has_signature_for_revision_id(revision_id):
2743
def verify_revision_signature(self, revision_id, gpg_strategy):
2744
if not self.has_signature_for_revision_id(revision_id):
2745
return gpg.SIGNATURE_NOT_SIGNED, None
2746
signature = self.get_signature_text(revision_id)
2748
testament = _mod_testament.Testament.from_revision(self, revision_id)
2749
plaintext = testament.as_short_text()
2751
return gpg_strategy.verify(signature, plaintext)
1721
2753
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1722
2754
self._ensure_real()
1723
2755
return self._real_repository.item_keys_introduced_by(revision_ids,
1724
2756
_files_pb=_files_pb)
1726
def revision_graph_can_have_wrong_parents(self):
1727
# The answer depends on the remote repo format.
1729
return self._real_repository.revision_graph_can_have_wrong_parents()
1731
2758
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1732
2759
self._ensure_real()
1733
2760
return self._real_repository._find_inconsistent_revision_parents(
2068
3106
def network_name(self):
2069
3107
return self._network_name
2071
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2072
return a_bzrdir.open_branch(name=name,
3109
def open(self, a_controldir, name=None, ignore_fallbacks=False):
3110
return a_controldir.open_branch(name=name,
2073
3111
ignore_fallbacks=ignore_fallbacks)
2075
def _vfs_initialize(self, a_bzrdir, name):
3113
def _vfs_initialize(self, a_controldir, name, append_revisions_only,
2076
3115
# Initialisation when using a local bzrdir object, or a non-vfs init
2077
3116
# method is not available on the server.
2078
3117
# self._custom_format is always set - the start of initialize ensures
2080
if isinstance(a_bzrdir, RemoteBzrDir):
2081
a_bzrdir._ensure_real()
2082
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3119
if isinstance(a_controldir, RemoteBzrDir):
3120
a_controldir._ensure_real()
3121
result = self._custom_format.initialize(a_controldir._real_bzrdir,
3122
name=name, append_revisions_only=append_revisions_only,
3123
repository=repository)
2085
3125
# We assume the bzrdir is parameterised; it may not be.
2086
result = self._custom_format.initialize(a_bzrdir, name)
2087
if (isinstance(a_bzrdir, RemoteBzrDir) and
3126
result = self._custom_format.initialize(a_controldir, name=name,
3127
append_revisions_only=append_revisions_only,
3128
repository=repository)
3129
if (isinstance(a_controldir, RemoteBzrDir) and
2088
3130
not isinstance(result, RemoteBranch)):
2089
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3131
result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
2093
def initialize(self, a_bzrdir, name=None):
3135
def initialize(self, a_controldir, name=None, repository=None,
3136
append_revisions_only=None):
3138
name = a_controldir._get_selected_branch()
2094
3139
# 1) get the network name to use.
2095
3140
if self._custom_format:
2096
3141
network_name = self._custom_format.network_name()
2098
# Select the current bzrlib default and ask for that.
2099
reference_bzrdir_format = bzrdir.format_registry.get('default')()
3143
# Select the current breezy default and ask for that.
3144
reference_bzrdir_format = controldir.format_registry.get('default')()
2100
3145
reference_format = reference_bzrdir_format.get_branch_format()
2101
3146
self._custom_format = reference_format
2102
3147
network_name = reference_format.network_name()
2103
3148
# Being asked to create on a non RemoteBzrDir:
2104
if not isinstance(a_bzrdir, RemoteBzrDir):
2105
return self._vfs_initialize(a_bzrdir, name=name)
2106
medium = a_bzrdir._client._medium
3149
if not isinstance(a_controldir, RemoteBzrDir):
3150
return self._vfs_initialize(a_controldir, name=name,
3151
append_revisions_only=append_revisions_only,
3152
repository=repository)
3153
medium = a_controldir._client._medium
2107
3154
if medium._is_remote_before((1, 13)):
2108
return self._vfs_initialize(a_bzrdir, name=name)
3155
return self._vfs_initialize(a_controldir, name=name,
3156
append_revisions_only=append_revisions_only,
3157
repository=repository)
2109
3158
# Creating on a remote bzr dir.
2110
3159
# 2) try direct creation via RPC
2111
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2112
if name is not None:
3160
path = a_controldir._path_for_remote_call(a_controldir._client)
2113
3162
# XXX JRV20100304: Support creating colocated branches
2114
3163
raise errors.NoColocatedBranchSupport(self)
2115
3164
verb = 'BzrDir.create_branch'
2117
response = a_bzrdir._call(verb, path, network_name)
3166
response = a_controldir._call(verb, path, network_name)
2118
3167
except errors.UnknownSmartMethod:
2119
3168
# Fallback - use vfs methods
2120
3169
medium._remember_remote_is_before((1, 13))
2121
return self._vfs_initialize(a_bzrdir, name=name)
3170
return self._vfs_initialize(a_controldir, name=name,
3171
append_revisions_only=append_revisions_only,
3172
repository=repository)
2122
3173
if response[0] != 'ok':
2123
3174
raise errors.UnexpectedSmartServerResponse(response)
2124
3175
# Turn the response into a RemoteRepository object.
2125
3176
format = RemoteBranchFormat(network_name=response[1])
2126
3177
repo_format = response_tuple_to_repo_format(response[3:])
2127
if response[2] == '':
2128
repo_bzrdir = a_bzrdir
3178
repo_path = response[2]
3179
if repository is not None:
3180
remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3181
url_diff = urlutils.relative_url(repository.user_url,
3184
raise AssertionError(
3185
'repository.user_url %r does not match URL from server '
3186
'response (%r + %r)'
3187
% (repository.user_url, a_controldir.user_url, repo_path))
3188
remote_repo = repository
2130
repo_bzrdir = RemoteBzrDir(
2131
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2133
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2134
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3191
repo_bzrdir = a_controldir
3193
repo_bzrdir = RemoteBzrDir(
3194
a_controldir.root_transport.clone(repo_path), a_controldir._format,
3195
a_controldir._client)
3196
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3197
remote_branch = RemoteBranch(a_controldir, remote_repo,
2135
3198
format=format, setup_stacking=False, name=name)
3199
if append_revisions_only:
3200
remote_branch.set_append_revisions_only(append_revisions_only)
2136
3201
# XXX: We know this is a new branch, so it must have revno 0, revid
2137
3202
# NULL_REVISION. Creating the branch locked would make this be unable
2138
3203
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2157
3222
self._ensure_real()
2158
3223
return self._custom_format.supports_set_append_revisions_only()
3225
def _use_default_local_heads_to_fetch(self):
3226
# If the branch format is a metadir format *and* its heads_to_fetch
3227
# implementation is not overridden vs the base class, we can use the
3228
# base class logic rather than use the heads_to_fetch RPC. This is
3229
# usually cheaper in terms of net round trips, as the last-revision and
3230
# tags info fetched is cached and would be fetched anyway.
3232
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3233
branch_class = self._custom_format._branch_class()
3234
heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
3235
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
3240
class RemoteBranchStore(_mod_config.IniFileStore):
3241
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3243
Note that this is specific to bzr-based formats.
3246
def __init__(self, branch):
3247
super(RemoteBranchStore, self).__init__()
3248
self.branch = branch
3250
self._real_store = None
3252
def external_url(self):
3253
return urlutils.join(self.branch.user_url, 'branch.conf')
3255
def _load_content(self):
3256
path = self.branch._remote_path()
3258
response, handler = self.branch._call_expecting_body(
3259
'Branch.get_config_file', path)
3260
except errors.UnknownSmartMethod:
3262
return self._real_store._load_content()
3263
if len(response) and response[0] != 'ok':
3264
raise errors.UnexpectedSmartServerResponse(response)
3265
return handler.read_body_bytes()
3267
def _save_content(self, content):
3268
path = self.branch._remote_path()
3270
response, handler = self.branch._call_with_body_bytes_expecting_body(
3271
'Branch.put_config_file', (path,
3272
self.branch._lock_token, self.branch._repo_lock_token),
3274
except errors.UnknownSmartMethod:
3276
return self._real_store._save_content(content)
3277
handler.cancel_read_body()
3278
if response != ('ok', ):
3279
raise errors.UnexpectedSmartServerResponse(response)
3281
def _ensure_real(self):
3282
self.branch._ensure_real()
3283
if self._real_store is None:
3284
self._real_store = _mod_config.BranchStore(self.branch)
2161
3287
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2162
3288
"""Branch stored on a server accessed by HPSS RPC.
2668
3838
_override_hook_target=self, **kwargs)
2670
3840
@needs_read_lock
2671
def push(self, target, overwrite=False, stop_revision=None):
3841
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
2672
3842
self._ensure_real()
2673
3843
return self._real_branch.push(
2674
target, overwrite=overwrite, stop_revision=stop_revision,
3844
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
2675
3845
_override_hook_source_branch=self)
3847
def peek_lock_mode(self):
3848
return self._lock_mode
2677
3850
def is_locked(self):
2678
3851
return self._lock_count >= 1
2680
3853
@needs_read_lock
3854
def revision_id_to_dotted_revno(self, revision_id):
3855
"""Given a revision id, return its dotted revno.
3857
:return: a tuple like (1,) or (400,1,3).
3860
response = self._call('Branch.revision_id_to_revno',
3861
self._remote_path(), revision_id)
3862
except errors.UnknownSmartMethod:
3864
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3865
if response[0] == 'ok':
3866
return tuple([int(x) for x in response[1:]])
3868
raise errors.UnexpectedSmartServerResponse(response)
2681
3871
def revision_id_to_revno(self, revision_id):
2683
return self._real_branch.revision_id_to_revno(revision_id)
3872
"""Given a revision id on the branch mainline, return its revno.
3877
response = self._call('Branch.revision_id_to_revno',
3878
self._remote_path(), revision_id)
3879
except errors.UnknownSmartMethod:
3881
return self._real_branch.revision_id_to_revno(revision_id)
3882
if response[0] == 'ok':
3883
if len(response) == 2:
3884
return int(response[1])
3885
raise NoSuchRevision(self, revision_id)
3887
raise errors.UnexpectedSmartServerResponse(response)
2685
3889
@needs_write_lock
2686
3890
def set_last_revision_info(self, revno, revision_id):
2687
3891
# XXX: These should be returned by the set_last_revision_info verb
2688
3892
old_revno, old_revid = self.last_revision_info()
2689
3893
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2690
revision_id = ensure_null(revision_id)
3894
if not revision_id or not isinstance(revision_id, basestring):
3895
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
2692
3897
response = self._call('Branch.set_last_revision_info',
2693
3898
self._remote_path(), self._lock_token, self._repo_lock_token,
2722
3927
except errors.UnknownSmartMethod:
2723
3928
medium._remember_remote_is_before((1, 6))
2724
3929
self._clear_cached_state_of_remote_branch_only()
2725
self.set_revision_history(self._lefthand_history(revision_id,
2726
last_rev=last_rev,other_branch=other_branch))
3930
graph = self.repository.get_graph()
3931
(last_revno, last_revid) = self.last_revision_info()
3932
known_revision_ids = [
3933
(last_revid, last_revno),
3934
(_mod_revision.NULL_REVISION, 0),
3936
if last_rev is not None:
3937
if not graph.is_ancestor(last_rev, revision_id):
3938
# our previous tip is not merged into stop_revision
3939
raise errors.DivergedBranches(self, other_branch)
3940
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
3941
self.set_last_revision_info(revno, revision_id)
2728
3943
def set_push_location(self, location):
3944
self._set_config_location('push_location', location)
3946
def heads_to_fetch(self):
3947
if self._format._use_default_local_heads_to_fetch():
3948
# We recognise this format, and its heads-to-fetch implementation
3949
# is the default one (tip + tags). In this case it's cheaper to
3950
# just use the default implementation rather than a special RPC as
3951
# the tip and tags data is cached.
3952
return branch.Branch.heads_to_fetch(self)
3953
medium = self._client._medium
3954
if medium._is_remote_before((2, 4)):
3955
return self._vfs_heads_to_fetch()
3957
return self._rpc_heads_to_fetch()
3958
except errors.UnknownSmartMethod:
3959
medium._remember_remote_is_before((2, 4))
3960
return self._vfs_heads_to_fetch()
3962
def _rpc_heads_to_fetch(self):
3963
response = self._call('Branch.heads_to_fetch', self._remote_path())
3964
if len(response) != 2:
3965
raise errors.UnexpectedSmartServerResponse(response)
3966
must_fetch, if_present_fetch = response
3967
return set(must_fetch), set(if_present_fetch)
3969
def _vfs_heads_to_fetch(self):
2729
3970
self._ensure_real()
2730
return self._real_branch.set_push_location(location)
3971
return self._real_branch.heads_to_fetch()
2733
3974
class RemoteConfig(object):
2788
4039
medium = self._branch._client._medium
2789
4040
if medium._is_remote_before((1, 14)):
2790
4041
return self._vfs_set_option(value, name, section)
4042
if isinstance(value, dict):
4043
if medium._is_remote_before((2, 2)):
4044
return self._vfs_set_option(value, name, section)
4045
return self._set_config_option_dict(value, name, section)
4047
return self._set_config_option(value, name, section)
4049
def _set_config_option(self, value, name, section):
2792
4051
path = self._branch._remote_path()
2793
4052
response = self._branch._client.call('Branch.set_config_option',
2794
4053
path, self._branch._lock_token, self._branch._repo_lock_token,
2795
4054
value.encode('utf8'), name, section or '')
2796
4055
except errors.UnknownSmartMethod:
4056
medium = self._branch._client._medium
2797
4057
medium._remember_remote_is_before((1, 14))
2798
4058
return self._vfs_set_option(value, name, section)
2799
4059
if response != ():
2800
4060
raise errors.UnexpectedSmartServerResponse(response)
4062
def _serialize_option_dict(self, option_dict):
4064
for key, value in option_dict.items():
4065
if isinstance(key, unicode):
4066
key = key.encode('utf8')
4067
if isinstance(value, unicode):
4068
value = value.encode('utf8')
4069
utf8_dict[key] = value
4070
return bencode.bencode(utf8_dict)
4072
def _set_config_option_dict(self, value, name, section):
4074
path = self._branch._remote_path()
4075
serialised_dict = self._serialize_option_dict(value)
4076
response = self._branch._client.call(
4077
'Branch.set_config_option_dict',
4078
path, self._branch._lock_token, self._branch._repo_lock_token,
4079
serialised_dict, name, section or '')
4080
except errors.UnknownSmartMethod:
4081
medium = self._branch._client._medium
4082
medium._remember_remote_is_before((2, 2))
4083
return self._vfs_set_option(value, name, section)
4085
raise errors.UnexpectedSmartServerResponse(response)
2802
4087
def _real_object(self):
2803
4088
self._branch._ensure_real()
2804
4089
return self._branch._real_branch
2881
4169
return context['path']
2882
except KeyError, key_err:
4170
except KeyError as key_err:
2884
4172
return err.error_args[0]
2885
except IndexError, idx_err:
4173
except IndexError as idx_err:
2887
4175
'Missing key %r in context %r', key_err.args[0], context)
2890
if err.error_verb == 'IncompatibleRepositories':
2891
raise errors.IncompatibleRepositories(err.error_args[0],
2892
err.error_args[1], err.error_args[2])
2893
elif err.error_verb == 'NoSuchRevision':
2894
raise NoSuchRevision(find('branch'), err.error_args[0])
2895
elif err.error_verb == 'nosuchrevision':
2896
raise NoSuchRevision(find('repository'), err.error_args[0])
2897
elif err.error_verb == 'nobranch':
2898
if len(err.error_args) >= 1:
2899
extra = err.error_args[0]
2902
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2904
elif err.error_verb == 'norepository':
2905
raise errors.NoRepositoryPresent(find('bzrdir'))
2906
elif err.error_verb == 'LockContention':
2907
raise errors.LockContention('(remote lock)')
2908
elif err.error_verb == 'UnlockableTransport':
2909
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2910
elif err.error_verb == 'LockFailed':
2911
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2912
elif err.error_verb == 'TokenMismatch':
2913
raise errors.TokenMismatch(find('token'), '(remote token)')
2914
elif err.error_verb == 'Diverged':
2915
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2916
elif err.error_verb == 'TipChangeRejected':
2917
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2918
elif err.error_verb == 'UnstackableBranchFormat':
2919
raise errors.UnstackableBranchFormat(*err.error_args)
2920
elif err.error_verb == 'UnstackableRepositoryFormat':
2921
raise errors.UnstackableRepositoryFormat(*err.error_args)
2922
elif err.error_verb == 'NotStacked':
2923
raise errors.NotStacked(branch=find('branch'))
2924
elif err.error_verb == 'PermissionDenied':
2926
if len(err.error_args) >= 2:
2927
extra = err.error_args[1]
2930
raise errors.PermissionDenied(path, extra=extra)
2931
elif err.error_verb == 'ReadError':
2933
raise errors.ReadError(path)
2934
elif err.error_verb == 'NoSuchFile':
2936
raise errors.NoSuchFile(path)
2937
elif err.error_verb == 'FileExists':
2938
raise errors.FileExists(err.error_args[0])
2939
elif err.error_verb == 'DirectoryNotEmpty':
2940
raise errors.DirectoryNotEmpty(err.error_args[0])
2941
elif err.error_verb == 'ShortReadvError':
2942
args = err.error_args
2943
raise errors.ShortReadvError(
2944
args[0], int(args[1]), int(args[2]), int(args[3]))
2945
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
4179
translator = error_translators.get(err.error_verb)
4183
raise translator(err, find, get_path)
4185
translator = no_context_error_translators.get(err.error_verb)
4187
raise errors.UnknownErrorFromSmartServer(err)
4189
raise translator(err)
4192
error_translators.register('NoSuchRevision',
4193
lambda err, find, get_path: NoSuchRevision(
4194
find('branch'), err.error_args[0]))
4195
error_translators.register('nosuchrevision',
4196
lambda err, find, get_path: NoSuchRevision(
4197
find('repository'), err.error_args[0]))
4199
def _translate_nobranch_error(err, find, get_path):
4200
if len(err.error_args) >= 1:
4201
extra = err.error_args[0]
4204
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4207
error_translators.register('nobranch', _translate_nobranch_error)
4208
error_translators.register('norepository',
4209
lambda err, find, get_path: errors.NoRepositoryPresent(
4211
error_translators.register('UnlockableTransport',
4212
lambda err, find, get_path: errors.UnlockableTransport(
4213
find('bzrdir').root_transport))
4214
error_translators.register('TokenMismatch',
4215
lambda err, find, get_path: errors.TokenMismatch(
4216
find('token'), '(remote token)'))
4217
error_translators.register('Diverged',
4218
lambda err, find, get_path: errors.DivergedBranches(
4219
find('branch'), find('other_branch')))
4220
error_translators.register('NotStacked',
4221
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4223
def _translate_PermissionDenied(err, find, get_path):
4225
if len(err.error_args) >= 2:
4226
extra = err.error_args[1]
4229
return errors.PermissionDenied(path, extra=extra)
4231
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4232
error_translators.register('ReadError',
4233
lambda err, find, get_path: errors.ReadError(get_path()))
4234
error_translators.register('NoSuchFile',
4235
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4236
error_translators.register('TokenLockingNotSupported',
4237
lambda err, find, get_path: errors.TokenLockingNotSupported(
4238
find('repository')))
4239
error_translators.register('UnsuspendableWriteGroup',
4240
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4241
repository=find('repository')))
4242
error_translators.register('UnresumableWriteGroup',
4243
lambda err, find, get_path: errors.UnresumableWriteGroup(
4244
repository=find('repository'), write_groups=err.error_args[0],
4245
reason=err.error_args[1]))
4246
no_context_error_translators.register('IncompatibleRepositories',
4247
lambda err: errors.IncompatibleRepositories(
4248
err.error_args[0], err.error_args[1], err.error_args[2]))
4249
no_context_error_translators.register('LockContention',
4250
lambda err: errors.LockContention('(remote lock)'))
4251
no_context_error_translators.register('LockFailed',
4252
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4253
no_context_error_translators.register('TipChangeRejected',
4254
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4255
no_context_error_translators.register('UnstackableBranchFormat',
4256
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4257
no_context_error_translators.register('UnstackableRepositoryFormat',
4258
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4259
no_context_error_translators.register('FileExists',
4260
lambda err: errors.FileExists(err.error_args[0]))
4261
no_context_error_translators.register('DirectoryNotEmpty',
4262
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4264
def _translate_short_readv_error(err):
4265
args = err.error_args
4266
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4269
no_context_error_translators.register('ShortReadvError',
4270
_translate_short_readv_error)
4272
def _translate_unicode_error(err):
2946
4273
encoding = str(err.error_args[0]) # encoding must always be a string
2947
4274
val = err.error_args[1]
2948
4275
start = int(err.error_args[2])