71
74
class RemoteBzrDir(BzrDir, _RpcHelper):
72
75
"""Control directory on a remote server, accessed via bzr:// or similar."""
74
def __init__(self, transport, _client=None):
77
def __init__(self, transport, format, _client=None):
75
78
"""Construct a RemoteBzrDir.
77
80
:param _client: Private parameter for testing. Disables probing and the
78
81
use of a real bzrdir.
80
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
83
BzrDir.__init__(self, transport, format)
81
84
# this object holds a delegated bzrdir that uses file-level operations
82
85
# to talk to the other side
83
86
self._real_bzrdir = None
257
270
the attributes rich_root_data and supports_tree_reference are set
258
271
on a per instance basis, and are not set (and should not be) at
274
:ivar _custom_format: If set, a specific concrete repository format that
275
will be used when initializing a repository with this
276
RemoteRepositoryFormat.
277
:ivar _creating_repo: If set, the repository object that this
278
RemoteRepositoryFormat was created for: it can be called into
279
to obtain data like the network name.
262
282
_matchingbzrdir = RemoteBzrDirFormat()
264
def initialize(self, a_bzrdir, shared=False):
265
if not isinstance(a_bzrdir, RemoteBzrDir):
285
repository.RepositoryFormat.__init__(self)
286
self._custom_format = None
287
self._network_name = None
288
self._creating_bzrdir = None
290
def _vfs_initialize(self, a_bzrdir, shared):
291
"""Helper for common code in initialize."""
292
if self._custom_format:
293
# Custom format requested
294
result = self._custom_format.initialize(a_bzrdir, shared=shared)
295
elif self._creating_bzrdir is not None:
296
# Use the format that the repository we were created to back
266
298
prior_repo = self._creating_bzrdir.open_repository()
267
299
prior_repo._ensure_real()
268
return prior_repo._real_repository._format.initialize(
300
result = prior_repo._real_repository._format.initialize(
269
301
a_bzrdir, shared=shared)
270
return a_bzrdir.create_repository(shared=shared)
303
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
304
# support remote initialization.
305
# We delegate to a real object at this point (as RemoteBzrDir
306
# delegate to the repository format which would lead to infinite
307
# recursion if we just called a_bzrdir.create_repository.
308
a_bzrdir._ensure_real()
309
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
310
if not isinstance(result, RemoteRepository):
311
return self.open(a_bzrdir)
315
def initialize(self, a_bzrdir, shared=False):
316
# Being asked to create on a non RemoteBzrDir:
317
if not isinstance(a_bzrdir, RemoteBzrDir):
318
return self._vfs_initialize(a_bzrdir, shared)
319
medium = a_bzrdir._client._medium
320
if medium._is_remote_before((1, 13)):
321
return self._vfs_initialize(a_bzrdir, shared)
322
# Creating on a remote bzr dir.
323
# 1) get the network name to use.
324
if self._custom_format:
325
network_name = self._custom_format.network_name()
327
# Select the current bzrlib default and ask for that.
328
reference_bzrdir_format = bzrdir.format_registry.get('default')()
329
reference_format = reference_bzrdir_format.repository_format
330
network_name = reference_format.network_name()
331
# 2) try direct creation via RPC
332
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
333
verb = 'BzrDir.create_repository'
339
response = a_bzrdir._call(verb, path, network_name, shared_str)
340
except errors.UnknownSmartMethod:
341
# Fallback - use vfs methods
342
return self._vfs_initialize(a_bzrdir, shared)
344
# Turn the response into a RemoteRepository object.
345
format = RemoteRepositoryFormat()
346
format.rich_root_data = (response[1] == 'yes')
347
format.supports_tree_reference = (response[2] == 'yes')
348
format.supports_external_lookups = (response[3] == 'yes')
349
format._network_name = response[4]
350
# Used to support creating a real format instance when needed.
351
format._creating_bzrdir = a_bzrdir
352
remote_repo = RemoteRepository(a_bzrdir, format)
353
format._creating_repo = remote_repo
272
356
def open(self, a_bzrdir):
273
357
if not isinstance(a_bzrdir, RemoteBzrDir):
289
373
raise errors.BadConversionTarget(
290
374
'Does not support nested trees', target_format)
376
def network_name(self):
377
if self._network_name:
378
return self._network_name
379
self._creating_repo._ensure_real()
380
return self._creating_repo._real_repository._format.network_name()
383
def _serializer(self):
384
# We should only be getting asked for the serializer for
385
# RemoteRepositoryFormat objects when the RemoteRepositoryFormat object
386
# is a concrete instance for a RemoteRepository. In this case we know
387
# the creating_repo and can use it to supply the serializer.
388
self._creating_repo._ensure_real()
389
return self._creating_repo._real_repository._format._serializer
293
392
class RemoteRepository(_RpcHelper):
294
393
"""Repository accessed over rpc.
1220
1343
raise errors.UnexpectedSmartServerResponse(response)
1346
class RemoteStreamSink(repository.StreamSink):
1348
def _insert_real(self, stream, src_format):
1349
self.target_repo._ensure_real()
1350
sink = self.target_repo._real_repository._get_sink()
1351
return sink.insert_stream(stream, src_format)
1353
def insert_stream(self, stream, src_format):
1354
repo = self.target_repo
1355
# Until we can handle deltas in stack repositories we can't hand all
1356
# the processing off to a remote server.
1357
if self.target_repo._fallback_repositories:
1358
return self._insert_real(stream, src_format)
1359
client = repo._client
1360
medium = client._medium
1361
if medium._is_remote_before((1,13)):
1362
# No possible way this can work.
1363
return self._insert_real(stream, src_format)
1364
path = repo.bzrdir._path_for_remote_call(client)
1365
# XXX: Ugly but important for correctness, *will* be fixed during 1.13
1366
# cycle. Pushing a stream that is interrupted results in a fallback to
1367
# the _real_repositories sink *with a partial stream*. Thats bad
1368
# because we insert less data than bzr expected. To avoid this we do a
1369
# trial push to make sure the verb is accessible, and do not fallback
1370
# when actually pushing the stream. A cleanup patch is going to look at
1371
# rewinding/restarting the stream/partial buffering etc.
1372
byte_stream = self._stream_to_byte_stream([], src_format)
1374
response = client.call_with_body_stream(
1375
('Repository.insert_stream', path), byte_stream)
1376
except errors.UnknownSmartMethod:
1377
medium._remember_remote_is_before((1,13))
1378
return self._insert_real(stream, src_format)
1379
byte_stream = self._stream_to_byte_stream(stream, src_format)
1380
response = client.call_with_body_stream(
1381
('Repository.insert_stream', path), byte_stream)
1382
if response[0][0] not in ('ok', ):
1383
raise errors.UnexpectedSmartServerResponse(response)
1385
def _stream_to_byte_stream(self, stream, src_format):
1387
pack_writer = pack.ContainerWriter(bytes.append)
1389
pack_writer.add_bytes_record(src_format.network_name(), '')
1391
def get_adapter(adapter_key):
1393
return adapters[adapter_key]
1395
adapter_factory = adapter_registry.get(adapter_key)
1396
adapter = adapter_factory(self)
1397
adapters[adapter_key] = adapter
1399
for substream_type, substream in stream:
1400
for record in substream:
1401
if record.storage_kind in ('chunked', 'fulltext'):
1402
serialised = record_to_fulltext_bytes(record)
1404
serialised = record.get_bytes_as(record.storage_kind)
1405
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1223
1414
class RemoteBranchLockableFiles(LockableFiles):
1224
1415
"""A 'LockableFiles' implementation that talks to a smart server.
1261
1452
return a_bzrdir.open_branch()
1263
1454
def initialize(self, a_bzrdir):
1264
return a_bzrdir.create_branch()
1455
# Delegate to a _real object here - the RemoteBzrDir format now
1456
# supports delegating to parameterised branch formats and as such
1457
# this RemoteBranchFormat method is only called when no specific format
1459
if not isinstance(a_bzrdir, RemoteBzrDir):
1460
result = a_bzrdir.create_branch()
1462
a_bzrdir._ensure_real()
1463
result = a_bzrdir._real_bzrdir.create_branch()
1464
if not isinstance(result, RemoteBranch):
1465
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1266
1468
def supports_tags(self):
1267
1469
# Remote branches might support tags, but we won't know until we
1581
1783
def _set_last_revision_descendant(self, revision_id, other_branch,
1582
1784
allow_diverged=False, allow_overwrite_descendant=False):
1785
# This performs additional work to meet the hook contract; while its
1786
# undesirable, we have to synthesise the revno to call the hook, and
1787
# not calling the hook is worse as it means changes can't be prevented.
1788
# Having calculated this though, we can't just call into
1789
# set_last_revision_info as a simple call, because there is a set_rh
1790
# hook that some folk may still be using.
1791
old_revno, old_revid = self.last_revision_info()
1792
history = self._lefthand_history(revision_id)
1793
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1583
1794
err_context = {'other_branch': other_branch}
1584
1795
response = self._call('Branch.set_last_revision_ex',
1585
1796
self._remote_path(), self._lock_token, self._repo_lock_token,
1590
1801
raise errors.UnexpectedSmartServerResponse(response)
1591
1802
new_revno, new_revision_id = response[1:]
1592
1803
self._last_revision_info_cache = new_revno, new_revision_id
1804
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1593
1805
if self._real_branch is not None:
1594
1806
cache = new_revno, new_revision_id
1595
1807
self._real_branch._last_revision_info_cache = cache
1597
1809
def _set_last_revision(self, revision_id):
1810
old_revno, old_revid = self.last_revision_info()
1811
# This performs additional work to meet the hook contract; while its
1812
# undesirable, we have to synthesise the revno to call the hook, and
1813
# not calling the hook is worse as it means changes can't be prevented.
1814
# Having calculated this though, we can't just call into
1815
# set_last_revision_info as a simple call, because there is a set_rh
1816
# hook that some folk may still be using.
1817
history = self._lefthand_history(revision_id)
1818
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1598
1819
self._clear_cached_state()
1599
1820
response = self._call('Branch.set_last_revision',
1600
1821
self._remote_path(), self._lock_token, self._repo_lock_token,
1602
1823
if response != ('ok',):
1603
1824
raise errors.UnexpectedSmartServerResponse(response)
1825
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1605
1827
@needs_write_lock
1606
1828
def set_revision_history(self, rev_history):
1613
1835
rev_id = rev_history[-1]
1614
1836
self._set_last_revision(rev_id)
1837
for hook in branch.Branch.hooks['set_rh']:
1838
hook(self, rev_history)
1615
1839
self._cache_revision_history(rev_history)
1617
1841
def get_parent(self):
1618
1842
self._ensure_real()
1619
1843
return self._real_branch.get_parent()
1845
def _get_parent_location(self):
1846
# Used by tests, when checking normalisation of given vs stored paths.
1848
return self._real_branch._get_parent_location()
1621
1850
def set_parent(self, url):
1622
1851
self._ensure_real()
1623
1852
return self._real_branch.set_parent(url)
1854
def _set_parent_location(self, url):
1855
# Used by tests, to poke bad urls into branch configurations
1857
self.set_parent(url)
1860
return self._real_branch._set_parent_location(url)
1625
1862
def set_stacked_on_url(self, stacked_location):
1626
1863
"""Set the URL this branch is stacked against.