1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
36
from bzrlib.branch import BranchReferenceFormat
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.errors import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
47
from bzrlib.util import bencode
50
class _RpcHelper(object):
51
"""Mixin class that helps with issuing RPCs."""
53
def _call(self, method, *args, **err_context):
55
return self._client.call(method, *args)
56
except errors.ErrorFromSmartServer, err:
57
self._translate_error(err, **err_context)
59
def _call_expecting_body(self, method, *args, **err_context):
61
return self._client.call_expecting_body(method, *args)
62
except errors.ErrorFromSmartServer, err:
63
self._translate_error(err, **err_context)
65
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
68
return self._client.call_with_body_bytes_expecting_body(
69
method, args, body_bytes)
70
except errors.ErrorFromSmartServer, err:
71
self._translate_error(err, **err_context)
74
def response_tuple_to_repo_format(response):
75
"""Convert a response tuple describing a repository format to a format."""
76
format = RemoteRepositoryFormat()
77
format._rich_root_data = (response[0] == 'yes')
78
format._supports_tree_reference = (response[1] == 'yes')
79
format._supports_external_lookups = (response[2] == 'yes')
80
format._network_name = response[3]
84
# Note: RemoteBzrDirFormat is in bzrdir.py
86
class RemoteBzrDir(BzrDir, _RpcHelper):
87
"""Control directory on a remote server, accessed via bzr:// or similar."""
89
def __init__(self, transport, format, _client=None):
90
"""Construct a RemoteBzrDir.
92
:param _client: Private parameter for testing. Disables probing and the
95
BzrDir.__init__(self, transport, format)
96
# this object holds a delegated bzrdir that uses file-level operations
97
# to talk to the other side
98
self._real_bzrdir = None
99
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
100
# create_branch for details.
101
self._next_open_branch_result = None
104
medium = transport.get_smart_medium()
105
self._client = client._SmartClient(medium)
107
self._client = _client
110
path = self._path_for_remote_call(self._client)
111
response = self._call('BzrDir.open', path)
112
if response not in [('yes',), ('no',)]:
113
raise errors.UnexpectedSmartServerResponse(response)
114
if response == ('no',):
115
raise errors.NotBranchError(path=transport.base)
117
def _ensure_real(self):
118
"""Ensure that there is a _real_bzrdir set.
120
Used before calls to self._real_bzrdir.
122
if not self._real_bzrdir:
123
self._real_bzrdir = BzrDir.open_from_transport(
124
self.root_transport, _server_formats=False)
125
self._format._network_name = \
126
self._real_bzrdir._format.network_name()
128
def _translate_error(self, err, **context):
129
_translate_error(err, bzrdir=self, **context)
131
def break_lock(self):
132
# Prevent aliasing problems in the next_open_branch_result cache.
133
# See create_branch for rationale.
134
self._next_open_branch_result = None
135
return BzrDir.break_lock(self)
137
def _vfs_cloning_metadir(self, require_stacking=False):
139
return self._real_bzrdir.cloning_metadir(
140
require_stacking=require_stacking)
142
def cloning_metadir(self, require_stacking=False):
143
medium = self._client._medium
144
if medium._is_remote_before((1, 13)):
145
return self._vfs_cloning_metadir(require_stacking=require_stacking)
146
verb = 'BzrDir.cloning_metadir'
151
path = self._path_for_remote_call(self._client)
153
response = self._call(verb, path, stacking)
154
except errors.UnknownSmartMethod:
155
medium._remember_remote_is_before((1, 13))
156
return self._vfs_cloning_metadir(require_stacking=require_stacking)
157
except errors.UnknownErrorFromSmartServer, err:
158
if err.error_tuple != ('BranchReference',):
160
# We need to resolve the branch reference to determine the
161
# cloning_metadir. This causes unnecessary RPCs to open the
162
# referenced branch (and bzrdir, etc) but only when the caller
163
# didn't already resolve the branch reference.
164
referenced_branch = self.open_branch()
165
return referenced_branch.bzrdir.cloning_metadir()
166
if len(response) != 3:
167
raise errors.UnexpectedSmartServerResponse(response)
168
control_name, repo_name, branch_info = response
169
if len(branch_info) != 2:
170
raise errors.UnexpectedSmartServerResponse(response)
171
branch_ref, branch_name = branch_info
172
format = bzrdir.network_format_registry.get(control_name)
174
format.repository_format = repository.network_format_registry.get(
176
if branch_ref == 'ref':
177
# XXX: we need possible_transports here to avoid reopening the
178
# connection to the referenced location
179
ref_bzrdir = BzrDir.open(branch_name)
180
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
181
format.set_branch_format(branch_format)
182
elif branch_ref == 'branch':
184
format.set_branch_format(
185
branch.network_format_registry.get(branch_name))
187
raise errors.UnexpectedSmartServerResponse(response)
190
def create_repository(self, shared=False):
191
# as per meta1 formats - just delegate to the format object which may
193
result = self._format.repository_format.initialize(self, shared)
194
if not isinstance(result, RemoteRepository):
195
return self.open_repository()
199
def destroy_repository(self):
200
"""See BzrDir.destroy_repository"""
202
self._real_bzrdir.destroy_repository()
204
def create_branch(self):
205
# as per meta1 formats - just delegate to the format object which may
207
real_branch = self._format.get_branch_format().initialize(self)
208
if not isinstance(real_branch, RemoteBranch):
209
result = RemoteBranch(self, self.find_repository(), real_branch)
212
# BzrDir.clone_on_transport() uses the result of create_branch but does
213
# not return it to its callers; we save approximately 8% of our round
214
# trips by handing the branch we created back to the first caller to
215
# open_branch rather than probing anew. Long term we need a API in
216
# bzrdir that doesn't discard result objects (like result_branch).
218
self._next_open_branch_result = result
221
def destroy_branch(self):
222
"""See BzrDir.destroy_branch"""
224
self._real_bzrdir.destroy_branch()
225
self._next_open_branch_result = None
227
def create_workingtree(self, revision_id=None, from_branch=None):
228
raise errors.NotLocalUrl(self.transport.base)
230
def find_branch_format(self):
231
"""Find the branch 'format' for this bzrdir.
233
This might be a synthetic object for e.g. RemoteBranch and SVN.
235
b = self.open_branch()
238
def get_branch_reference(self):
239
"""See BzrDir.get_branch_reference()."""
240
response = self._get_branch_reference()
241
if response[0] == 'ref':
246
def _get_branch_reference(self):
247
path = self._path_for_remote_call(self._client)
248
medium = self._client._medium
249
if not medium._is_remote_before((1, 13)):
251
response = self._call('BzrDir.open_branchV2', path)
252
if response[0] not in ('ref', 'branch'):
253
raise errors.UnexpectedSmartServerResponse(response)
255
except errors.UnknownSmartMethod:
256
medium._remember_remote_is_before((1, 13))
257
response = self._call('BzrDir.open_branch', path)
258
if response[0] != 'ok':
259
raise errors.UnexpectedSmartServerResponse(response)
260
if response[1] != '':
261
return ('ref', response[1])
263
return ('branch', '')
265
def _get_tree_branch(self):
266
"""See BzrDir._get_tree_branch()."""
267
return None, self.open_branch()
269
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
271
raise NotImplementedError('unsupported flag support not implemented yet.')
272
if self._next_open_branch_result is not None:
273
# See create_branch for details.
274
result = self._next_open_branch_result
275
self._next_open_branch_result = None
277
response = self._get_branch_reference()
278
if response[0] == 'ref':
279
# a branch reference, use the existing BranchReference logic.
280
format = BranchReferenceFormat()
281
return format.open(self, _found=True, location=response[1],
282
ignore_fallbacks=ignore_fallbacks)
283
branch_format_name = response[1]
284
if not branch_format_name:
285
branch_format_name = None
286
format = RemoteBranchFormat(network_name=branch_format_name)
287
return RemoteBranch(self, self.find_repository(), format=format,
288
setup_stacking=not ignore_fallbacks)
290
def _open_repo_v1(self, path):
291
verb = 'BzrDir.find_repository'
292
response = self._call(verb, path)
293
if response[0] != 'ok':
294
raise errors.UnexpectedSmartServerResponse(response)
295
# servers that only support the v1 method don't support external
298
repo = self._real_bzrdir.open_repository()
299
response = response + ('no', repo._format.network_name())
300
return response, repo
302
def _open_repo_v2(self, path):
303
verb = 'BzrDir.find_repositoryV2'
304
response = self._call(verb, path)
305
if response[0] != 'ok':
306
raise errors.UnexpectedSmartServerResponse(response)
308
repo = self._real_bzrdir.open_repository()
309
response = response + (repo._format.network_name(),)
310
return response, repo
312
def _open_repo_v3(self, path):
313
verb = 'BzrDir.find_repositoryV3'
314
medium = self._client._medium
315
if medium._is_remote_before((1, 13)):
316
raise errors.UnknownSmartMethod(verb)
318
response = self._call(verb, path)
319
except errors.UnknownSmartMethod:
320
medium._remember_remote_is_before((1, 13))
322
if response[0] != 'ok':
323
raise errors.UnexpectedSmartServerResponse(response)
324
return response, None
326
def open_repository(self):
327
path = self._path_for_remote_call(self._client)
329
for probe in [self._open_repo_v3, self._open_repo_v2,
332
response, real_repo = probe(path)
334
except errors.UnknownSmartMethod:
337
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
338
if response[0] != 'ok':
339
raise errors.UnexpectedSmartServerResponse(response)
340
if len(response) != 6:
341
raise SmartProtocolError('incorrect response length %s' % (response,))
342
if response[1] == '':
343
# repo is at this dir.
344
format = response_tuple_to_repo_format(response[2:])
345
# Used to support creating a real format instance when needed.
346
format._creating_bzrdir = self
347
remote_repo = RemoteRepository(self, format)
348
format._creating_repo = remote_repo
349
if real_repo is not None:
350
remote_repo._set_real_repository(real_repo)
353
raise errors.NoRepositoryPresent(self)
355
def open_workingtree(self, recommend_upgrade=True):
357
if self._real_bzrdir.has_workingtree():
358
raise errors.NotLocalUrl(self.root_transport)
360
raise errors.NoWorkingTree(self.root_transport.base)
362
def _path_for_remote_call(self, client):
363
"""Return the path to be used for this bzrdir in a remote call."""
364
return client.remote_path_from_transport(self.root_transport)
366
def get_branch_transport(self, branch_format):
368
return self._real_bzrdir.get_branch_transport(branch_format)
370
def get_repository_transport(self, repository_format):
372
return self._real_bzrdir.get_repository_transport(repository_format)
374
def get_workingtree_transport(self, workingtree_format):
376
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
378
def can_convert_format(self):
379
"""Upgrading of remote bzrdirs is not supported yet."""
382
def needs_format_conversion(self, format=None):
383
"""Upgrading of remote bzrdirs is not supported yet."""
385
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
386
% 'needs_format_conversion(format=None)')
389
def clone(self, url, revision_id=None, force_new_repo=False,
390
preserve_stacking=False):
392
return self._real_bzrdir.clone(url, revision_id=revision_id,
393
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
395
def get_config(self):
397
return self._real_bzrdir.get_config()
400
class RemoteRepositoryFormat(repository.RepositoryFormat):
401
"""Format for repositories accessed over a _SmartClient.
403
Instances of this repository are represented by RemoteRepository
406
The RemoteRepositoryFormat is parameterized during construction
407
to reflect the capabilities of the real, remote format. Specifically
408
the attributes rich_root_data and supports_tree_reference are set
409
on a per instance basis, and are not set (and should not be) at
412
:ivar _custom_format: If set, a specific concrete repository format that
413
will be used when initializing a repository with this
414
RemoteRepositoryFormat.
415
:ivar _creating_repo: If set, the repository object that this
416
RemoteRepositoryFormat was created for: it can be called into
417
to obtain data like the network name.
420
_matchingbzrdir = RemoteBzrDirFormat()
423
repository.RepositoryFormat.__init__(self)
424
self._custom_format = None
425
self._network_name = None
426
self._creating_bzrdir = None
427
self._supports_external_lookups = None
428
self._supports_tree_reference = None
429
self._rich_root_data = None
432
def fast_deltas(self):
434
return self._custom_format.fast_deltas
437
def rich_root_data(self):
438
if self._rich_root_data is None:
440
self._rich_root_data = self._custom_format.rich_root_data
441
return self._rich_root_data
444
def supports_external_lookups(self):
445
if self._supports_external_lookups is None:
447
self._supports_external_lookups = \
448
self._custom_format.supports_external_lookups
449
return self._supports_external_lookups
452
def supports_tree_reference(self):
453
if self._supports_tree_reference is None:
455
self._supports_tree_reference = \
456
self._custom_format.supports_tree_reference
457
return self._supports_tree_reference
459
def _vfs_initialize(self, a_bzrdir, shared):
460
"""Helper for common code in initialize."""
461
if self._custom_format:
462
# Custom format requested
463
result = self._custom_format.initialize(a_bzrdir, shared=shared)
464
elif self._creating_bzrdir is not None:
465
# Use the format that the repository we were created to back
467
prior_repo = self._creating_bzrdir.open_repository()
468
prior_repo._ensure_real()
469
result = prior_repo._real_repository._format.initialize(
470
a_bzrdir, shared=shared)
472
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
473
# support remote initialization.
474
# We delegate to a real object at this point (as RemoteBzrDir
475
# delegate to the repository format which would lead to infinite
476
# recursion if we just called a_bzrdir.create_repository.
477
a_bzrdir._ensure_real()
478
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
479
if not isinstance(result, RemoteRepository):
480
return self.open(a_bzrdir)
484
def initialize(self, a_bzrdir, shared=False):
485
# Being asked to create on a non RemoteBzrDir:
486
if not isinstance(a_bzrdir, RemoteBzrDir):
487
return self._vfs_initialize(a_bzrdir, shared)
488
medium = a_bzrdir._client._medium
489
if medium._is_remote_before((1, 13)):
490
return self._vfs_initialize(a_bzrdir, shared)
491
# Creating on a remote bzr dir.
492
# 1) get the network name to use.
493
if self._custom_format:
494
network_name = self._custom_format.network_name()
496
# Select the current bzrlib default and ask for that.
497
reference_bzrdir_format = bzrdir.format_registry.get('default')()
498
reference_format = reference_bzrdir_format.repository_format
499
network_name = reference_format.network_name()
500
# 2) try direct creation via RPC
501
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
502
verb = 'BzrDir.create_repository'
508
response = a_bzrdir._call(verb, path, network_name, shared_str)
509
except errors.UnknownSmartMethod:
510
# Fallback - use vfs methods
511
medium._remember_remote_is_before((1, 13))
512
return self._vfs_initialize(a_bzrdir, shared)
514
# Turn the response into a RemoteRepository object.
515
format = response_tuple_to_repo_format(response[1:])
516
# Used to support creating a real format instance when needed.
517
format._creating_bzrdir = a_bzrdir
518
remote_repo = RemoteRepository(a_bzrdir, format)
519
format._creating_repo = remote_repo
522
def open(self, a_bzrdir):
523
if not isinstance(a_bzrdir, RemoteBzrDir):
524
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
525
return a_bzrdir.open_repository()
527
def _ensure_real(self):
528
if self._custom_format is None:
529
self._custom_format = repository.network_format_registry.get(
533
def _fetch_order(self):
535
return self._custom_format._fetch_order
538
def _fetch_uses_deltas(self):
540
return self._custom_format._fetch_uses_deltas
543
def _fetch_reconcile(self):
545
return self._custom_format._fetch_reconcile
547
def get_format_description(self):
548
return 'bzr remote repository'
550
def __eq__(self, other):
551
return self.__class__ is other.__class__
553
def check_conversion_target(self, target_format):
554
if self.rich_root_data and not target_format.rich_root_data:
555
raise errors.BadConversionTarget(
556
'Does not support rich root data.', target_format)
557
if (self.supports_tree_reference and
558
not getattr(target_format, 'supports_tree_reference', False)):
559
raise errors.BadConversionTarget(
560
'Does not support nested trees', target_format)
562
def network_name(self):
563
if self._network_name:
564
return self._network_name
565
self._creating_repo._ensure_real()
566
return self._creating_repo._real_repository._format.network_name()
569
def _serializer(self):
571
return self._custom_format._serializer
574
class RemoteRepository(_RpcHelper):
575
"""Repository accessed over rpc.
577
For the moment most operations are performed using local transport-backed
581
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
582
"""Create a RemoteRepository instance.
584
:param remote_bzrdir: The bzrdir hosting this repository.
585
:param format: The RemoteFormat object to use.
586
:param real_repository: If not None, a local implementation of the
587
repository logic for the repository, usually accessing the data
589
:param _client: Private testing parameter - override the smart client
590
to be used by the repository.
593
self._real_repository = real_repository
595
self._real_repository = None
596
self.bzrdir = remote_bzrdir
598
self._client = remote_bzrdir._client
600
self._client = _client
601
self._format = format
602
self._lock_mode = None
603
self._lock_token = None
605
self._leave_lock = False
606
self._unstacked_provider = graph.CachingParentsProvider(
607
get_parent_map=self._get_parent_map_rpc)
608
self._unstacked_provider.disable_cache()
610
# These depend on the actual remote format, so force them off for
611
# maximum compatibility. XXX: In future these should depend on the
612
# remote repository instance, but this is irrelevant until we perform
613
# reconcile via an RPC call.
614
self._reconcile_does_inventory_gc = False
615
self._reconcile_fixes_text_parents = False
616
self._reconcile_backsup_inventory = False
617
self.base = self.bzrdir.transport.base
618
# Additional places to query for data.
619
self._fallback_repositories = []
622
return "%s(%s)" % (self.__class__.__name__, self.base)
626
def abort_write_group(self, suppress_errors=False):
627
"""Complete a write group on the decorated repository.
629
Smart methods peform operations in a single step so this api
630
is not really applicable except as a compatibility thunk
631
for older plugins that don't use e.g. the CommitBuilder
634
:param suppress_errors: see Repository.abort_write_group.
637
return self._real_repository.abort_write_group(
638
suppress_errors=suppress_errors)
640
def commit_write_group(self):
641
"""Complete a write group on the decorated repository.
643
Smart methods peform operations in a single step so this api
644
is not really applicable except as a compatibility thunk
645
for older plugins that don't use e.g. the CommitBuilder
649
return self._real_repository.commit_write_group()
651
def resume_write_group(self, tokens):
653
return self._real_repository.resume_write_group(tokens)
655
def suspend_write_group(self):
657
return self._real_repository.suspend_write_group()
659
def _ensure_real(self):
660
"""Ensure that there is a _real_repository set.
662
Used before calls to self._real_repository.
664
Note that _ensure_real causes many roundtrips to the server which are
665
not desirable, and prevents the use of smart one-roundtrip RPC's to
666
perform complex operations (such as accessing parent data, streaming
667
revisions etc). Adding calls to _ensure_real should only be done when
668
bringing up new functionality, adding fallbacks for smart methods that
669
require a fallback path, and never to replace an existing smart method
670
invocation. If in doubt chat to the bzr network team.
672
if self._real_repository is None:
673
self.bzrdir._ensure_real()
674
self._set_real_repository(
675
self.bzrdir._real_bzrdir.open_repository())
677
def _translate_error(self, err, **context):
678
self.bzrdir._translate_error(err, repository=self, **context)
680
def find_text_key_references(self):
681
"""Find the text key references within the repository.
683
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
684
revision_ids. Each altered file-ids has the exact revision_ids that
685
altered it listed explicitly.
686
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
687
to whether they were referred to by the inventory of the
688
revision_id that they contain. The inventory texts from all present
689
revision ids are assessed to generate this report.
692
return self._real_repository.find_text_key_references()
694
def _generate_text_key_index(self):
695
"""Generate a new text key index for the repository.
697
This is an expensive function that will take considerable time to run.
699
:return: A dict mapping (file_id, revision_id) tuples to a list of
700
parents, also (file_id, revision_id) tuples.
703
return self._real_repository._generate_text_key_index()
705
def _get_revision_graph(self, revision_id):
706
"""Private method for using with old (< 1.2) servers to fallback."""
707
if revision_id is None:
709
elif revision.is_null(revision_id):
712
path = self.bzrdir._path_for_remote_call(self._client)
713
response = self._call_expecting_body(
714
'Repository.get_revision_graph', path, revision_id)
715
response_tuple, response_handler = response
716
if response_tuple[0] != 'ok':
717
raise errors.UnexpectedSmartServerResponse(response_tuple)
718
coded = response_handler.read_body_bytes()
720
# no revisions in this repository!
722
lines = coded.split('\n')
725
d = tuple(line.split())
726
revision_graph[d[0]] = d[1:]
728
return revision_graph
731
"""See Repository._get_sink()."""
732
return RemoteStreamSink(self)
734
def _get_source(self, to_format):
735
"""Return a source for streaming from this repository."""
736
return RemoteStreamSource(self, to_format)
738
def has_revision(self, revision_id):
739
"""See Repository.has_revision()."""
740
if revision_id == NULL_REVISION:
741
# The null revision is always present.
743
path = self.bzrdir._path_for_remote_call(self._client)
744
response = self._call('Repository.has_revision', path, revision_id)
745
if response[0] not in ('yes', 'no'):
746
raise errors.UnexpectedSmartServerResponse(response)
747
if response[0] == 'yes':
749
for fallback_repo in self._fallback_repositories:
750
if fallback_repo.has_revision(revision_id):
754
def has_revisions(self, revision_ids):
755
"""See Repository.has_revisions()."""
756
# FIXME: This does many roundtrips, particularly when there are
757
# fallback repositories. -- mbp 20080905
759
for revision_id in revision_ids:
760
if self.has_revision(revision_id):
761
result.add(revision_id)
764
def has_same_location(self, other):
765
return (self.__class__ is other.__class__ and
766
self.bzrdir.transport.base == other.bzrdir.transport.base)
768
def get_graph(self, other_repository=None):
769
"""Return the graph for this repository format"""
770
parents_provider = self._make_parents_provider(other_repository)
771
return graph.Graph(parents_provider)
773
def gather_stats(self, revid=None, committers=None):
774
"""See Repository.gather_stats()."""
775
path = self.bzrdir._path_for_remote_call(self._client)
776
# revid can be None to indicate no revisions, not just NULL_REVISION
777
if revid is None or revision.is_null(revid):
781
if committers is None or not committers:
782
fmt_committers = 'no'
784
fmt_committers = 'yes'
785
response_tuple, response_handler = self._call_expecting_body(
786
'Repository.gather_stats', path, fmt_revid, fmt_committers)
787
if response_tuple[0] != 'ok':
788
raise errors.UnexpectedSmartServerResponse(response_tuple)
790
body = response_handler.read_body_bytes()
792
for line in body.split('\n'):
795
key, val_text = line.split(':')
796
if key in ('revisions', 'size', 'committers'):
797
result[key] = int(val_text)
798
elif key in ('firstrev', 'latestrev'):
799
values = val_text.split(' ')[1:]
800
result[key] = (float(values[0]), long(values[1]))
804
def find_branches(self, using=False):
805
"""See Repository.find_branches()."""
806
# should be an API call to the server.
808
return self._real_repository.find_branches(using=using)
810
def get_physical_lock_status(self):
811
"""See Repository.get_physical_lock_status()."""
812
# should be an API call to the server.
814
return self._real_repository.get_physical_lock_status()
816
def is_in_write_group(self):
817
"""Return True if there is an open write group.
819
write groups are only applicable locally for the smart server..
821
if self._real_repository:
822
return self._real_repository.is_in_write_group()
825
return self._lock_count >= 1
828
"""See Repository.is_shared()."""
829
path = self.bzrdir._path_for_remote_call(self._client)
830
response = self._call('Repository.is_shared', path)
831
if response[0] not in ('yes', 'no'):
832
raise SmartProtocolError('unexpected response code %s' % (response,))
833
return response[0] == 'yes'
835
def is_write_locked(self):
836
return self._lock_mode == 'w'
839
# wrong eventually - want a local lock cache context
840
if not self._lock_mode:
841
self._lock_mode = 'r'
843
self._unstacked_provider.enable_cache(cache_misses=True)
844
if self._real_repository is not None:
845
self._real_repository.lock_read()
847
self._lock_count += 1
848
for repo in self._fallback_repositories:
851
def _remote_lock_write(self, token):
852
path = self.bzrdir._path_for_remote_call(self._client)
855
err_context = {'token': token}
856
response = self._call('Repository.lock_write', path, token,
858
if response[0] == 'ok':
862
raise errors.UnexpectedSmartServerResponse(response)
864
def lock_write(self, token=None, _skip_rpc=False):
865
if not self._lock_mode:
867
if self._lock_token is not None:
868
if token != self._lock_token:
869
raise errors.TokenMismatch(token, self._lock_token)
870
self._lock_token = token
872
self._lock_token = self._remote_lock_write(token)
873
# if self._lock_token is None, then this is something like packs or
874
# svn where we don't get to lock the repo, or a weave style repository
875
# where we cannot lock it over the wire and attempts to do so will
877
if self._real_repository is not None:
878
self._real_repository.lock_write(token=self._lock_token)
879
if token is not None:
880
self._leave_lock = True
882
self._leave_lock = False
883
self._lock_mode = 'w'
885
self._unstacked_provider.enable_cache(cache_misses=False)
886
elif self._lock_mode == 'r':
887
raise errors.ReadOnlyError(self)
889
self._lock_count += 1
890
for repo in self._fallback_repositories:
891
# Writes don't affect fallback repos
893
return self._lock_token or None
895
def leave_lock_in_place(self):
896
if not self._lock_token:
897
raise NotImplementedError(self.leave_lock_in_place)
898
self._leave_lock = True
900
def dont_leave_lock_in_place(self):
901
if not self._lock_token:
902
raise NotImplementedError(self.dont_leave_lock_in_place)
903
self._leave_lock = False
905
def _set_real_repository(self, repository):
906
"""Set the _real_repository for this repository.
908
:param repository: The repository to fallback to for non-hpss
909
implemented operations.
911
if self._real_repository is not None:
912
# Replacing an already set real repository.
913
# We cannot do this [currently] if the repository is locked -
914
# synchronised state might be lost.
916
raise AssertionError('_real_repository is already set')
917
if isinstance(repository, RemoteRepository):
918
raise AssertionError()
919
self._real_repository = repository
920
# three code paths happen here:
921
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
922
# up stacking. In this case self._fallback_repositories is [], and the
923
# real repo is already setup. Preserve the real repo and
924
# RemoteRepository.add_fallback_repository will avoid adding
926
# 2) new servers, RemoteBranch.open() sets up stacking, and when
927
# ensure_real is triggered from a branch, the real repository to
928
# set already has a matching list with separate instances, but
929
# as they are also RemoteRepositories we don't worry about making the
930
# lists be identical.
931
# 3) new servers, RemoteRepository.ensure_real is triggered before
932
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
933
# and need to populate it.
934
if (self._fallback_repositories and
935
len(self._real_repository._fallback_repositories) !=
936
len(self._fallback_repositories)):
937
if len(self._real_repository._fallback_repositories):
938
raise AssertionError(
939
"cannot cleanly remove existing _fallback_repositories")
940
for fb in self._fallback_repositories:
941
self._real_repository.add_fallback_repository(fb)
942
if self._lock_mode == 'w':
943
# if we are already locked, the real repository must be able to
944
# acquire the lock with our token.
945
self._real_repository.lock_write(self._lock_token)
946
elif self._lock_mode == 'r':
947
self._real_repository.lock_read()
949
def start_write_group(self):
950
"""Start a write group on the decorated repository.
952
Smart methods peform operations in a single step so this api
953
is not really applicable except as a compatibility thunk
954
for older plugins that don't use e.g. the CommitBuilder
958
return self._real_repository.start_write_group()
960
def _unlock(self, token):
961
path = self.bzrdir._path_for_remote_call(self._client)
963
# with no token the remote repository is not persistently locked.
965
err_context = {'token': token}
966
response = self._call('Repository.unlock', path, token,
968
if response == ('ok',):
971
raise errors.UnexpectedSmartServerResponse(response)
974
if not self._lock_count:
975
raise errors.LockNotHeld(self)
976
self._lock_count -= 1
977
if self._lock_count > 0:
979
self._unstacked_provider.disable_cache()
980
old_mode = self._lock_mode
981
self._lock_mode = None
983
# The real repository is responsible at present for raising an
984
# exception if it's in an unfinished write group. However, it
985
# normally will *not* actually remove the lock from disk - that's
986
# done by the server on receiving the Repository.unlock call.
987
# This is just to let the _real_repository stay up to date.
988
if self._real_repository is not None:
989
self._real_repository.unlock()
991
# The rpc-level lock should be released even if there was a
992
# problem releasing the vfs-based lock.
994
# Only write-locked repositories need to make a remote method
995
# call to perfom the unlock.
996
old_token = self._lock_token
997
self._lock_token = None
998
if not self._leave_lock:
999
self._unlock(old_token)
1001
def break_lock(self):
1002
# should hand off to the network
1004
return self._real_repository.break_lock()
1006
def _get_tarball(self, compression):
1007
"""Return a TemporaryFile containing a repository tarball.
1009
Returns None if the server does not support sending tarballs.
1012
path = self.bzrdir._path_for_remote_call(self._client)
1014
response, protocol = self._call_expecting_body(
1015
'Repository.tarball', path, compression)
1016
except errors.UnknownSmartMethod:
1017
protocol.cancel_read_body()
1019
if response[0] == 'ok':
1020
# Extract the tarball and return it
1021
t = tempfile.NamedTemporaryFile()
1022
# TODO: rpc layer should read directly into it...
1023
t.write(protocol.read_body_bytes())
1026
raise errors.UnexpectedSmartServerResponse(response)
1028
def sprout(self, to_bzrdir, revision_id=None):
1029
# TODO: Option to control what format is created?
1031
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1033
dest_repo.fetch(self, revision_id=revision_id)
1036
### These methods are just thin shims to the VFS object for now.
1038
def revision_tree(self, revision_id):
1040
return self._real_repository.revision_tree(revision_id)
1042
def get_serializer_format(self):
1044
return self._real_repository.get_serializer_format()
1046
def get_commit_builder(self, branch, parents, config, timestamp=None,
1047
timezone=None, committer=None, revprops=None,
1049
# FIXME: It ought to be possible to call this without immediately
1050
# triggering _ensure_real. For now it's the easiest thing to do.
1052
real_repo = self._real_repository
1053
builder = real_repo.get_commit_builder(branch, parents,
1054
config, timestamp=timestamp, timezone=timezone,
1055
committer=committer, revprops=revprops, revision_id=revision_id)
1058
def add_fallback_repository(self, repository):
1059
"""Add a repository to use for looking up data not held locally.
1061
:param repository: A repository.
1063
if not self._format.supports_external_lookups:
1064
raise errors.UnstackableRepositoryFormat(
1065
self._format.network_name(), self.base)
1066
# We need to accumulate additional repositories here, to pass them in
1069
self._fallback_repositories.append(repository)
1070
# If self._real_repository was parameterised already (e.g. because a
1071
# _real_branch had its get_stacked_on_url method called), then the
1072
# repository to be added may already be in the _real_repositories list.
1073
if self._real_repository is not None:
1074
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1075
self._real_repository._fallback_repositories]
1076
if repository.bzrdir.root_transport.base not in fallback_locations:
1077
self._real_repository.add_fallback_repository(repository)
1079
def add_inventory(self, revid, inv, parents):
1081
return self._real_repository.add_inventory(revid, inv, parents)
1083
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1086
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1087
delta, new_revision_id, parents)
1089
def add_revision(self, rev_id, rev, inv=None, config=None):
1091
return self._real_repository.add_revision(
1092
rev_id, rev, inv=inv, config=config)
1095
def get_inventory(self, revision_id):
1097
return self._real_repository.get_inventory(revision_id)
1099
def iter_inventories(self, revision_ids):
1101
return self._real_repository.iter_inventories(revision_ids)
1104
def get_revision(self, revision_id):
1106
return self._real_repository.get_revision(revision_id)
1108
def get_transaction(self):
1110
return self._real_repository.get_transaction()
1113
def clone(self, a_bzrdir, revision_id=None):
1115
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1117
def make_working_trees(self):
1118
"""See Repository.make_working_trees"""
1120
return self._real_repository.make_working_trees()
1122
def refresh_data(self):
1123
"""Re-read any data needed to to synchronise with disk.
1125
This method is intended to be called after another repository instance
1126
(such as one used by a smart server) has inserted data into the
1127
repository. It may not be called during a write group, but may be
1128
called at any other time.
1130
if self.is_in_write_group():
1131
raise errors.InternalBzrError(
1132
"May not refresh_data while in a write group.")
1133
if self._real_repository is not None:
1134
self._real_repository.refresh_data()
1136
def revision_ids_to_search_result(self, result_set):
1137
"""Convert a set of revision ids to a graph SearchResult."""
1138
result_parents = set()
1139
for parents in self.get_graph().get_parent_map(
1140
result_set).itervalues():
1141
result_parents.update(parents)
1142
included_keys = result_set.intersection(result_parents)
1143
start_keys = result_set.difference(included_keys)
1144
exclude_keys = result_parents.difference(result_set)
1145
result = graph.SearchResult(start_keys, exclude_keys,
1146
len(result_set), result_set)
1150
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1151
"""Return the revision ids that other has that this does not.
1153
These are returned in topological order.
1155
revision_id: only return revision ids included by revision_id.
1157
return repository.InterRepository.get(
1158
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1160
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1162
# No base implementation to use as RemoteRepository is not a subclass
1163
# of Repository; so this is a copy of Repository.fetch().
1164
if fetch_spec is not None and revision_id is not None:
1165
raise AssertionError(
1166
"fetch_spec and revision_id are mutually exclusive.")
1167
if self.is_in_write_group():
1168
raise errors.InternalBzrError(
1169
"May not fetch while in a write group.")
1170
# fast path same-url fetch operations
1171
if self.has_same_location(source) and fetch_spec is None:
1172
# check that last_revision is in 'from' and then return a
1174
if (revision_id is not None and
1175
not revision.is_null(revision_id)):
1176
self.get_revision(revision_id)
1178
# if there is no specific appropriate InterRepository, this will get
1179
# the InterRepository base class, which raises an
1180
# IncompatibleRepositories when asked to fetch.
1181
inter = repository.InterRepository.get(source, self)
1182
return inter.fetch(revision_id=revision_id, pb=pb,
1183
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1185
def create_bundle(self, target, base, fileobj, format=None):
1187
self._real_repository.create_bundle(target, base, fileobj, format)
1190
def get_ancestry(self, revision_id, topo_sorted=True):
1192
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1194
def fileids_altered_by_revision_ids(self, revision_ids):
1196
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1198
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1200
return self._real_repository._get_versioned_file_checker(
1201
revisions, revision_versions_cache)
1203
def iter_files_bytes(self, desired_files):
1204
"""See Repository.iter_file_bytes.
1207
return self._real_repository.iter_files_bytes(desired_files)
1209
def get_parent_map(self, revision_ids):
1210
"""See bzrlib.Graph.get_parent_map()."""
1211
return self._make_parents_provider().get_parent_map(revision_ids)
1213
def _get_parent_map_rpc(self, keys):
1214
"""Helper for get_parent_map that performs the RPC."""
1215
medium = self._client._medium
1216
if medium._is_remote_before((1, 2)):
1217
# We already found out that the server can't understand
1218
# Repository.get_parent_map requests, so just fetch the whole
1221
# Note that this reads the whole graph, when only some keys are
1222
# wanted. On this old server there's no way (?) to get them all
1223
# in one go, and the user probably will have seen a warning about
1224
# the server being old anyhow.
1225
rg = self._get_revision_graph(None)
1226
# There is an api discrepency between get_parent_map and
1227
# get_revision_graph. Specifically, a "key:()" pair in
1228
# get_revision_graph just means a node has no parents. For
1229
# "get_parent_map" it means the node is a ghost. So fix up the
1230
# graph to correct this.
1231
# https://bugs.launchpad.net/bzr/+bug/214894
1232
# There is one other "bug" which is that ghosts in
1233
# get_revision_graph() are not returned at all. But we won't worry
1234
# about that for now.
1235
for node_id, parent_ids in rg.iteritems():
1236
if parent_ids == ():
1237
rg[node_id] = (NULL_REVISION,)
1238
rg[NULL_REVISION] = ()
1243
raise ValueError('get_parent_map(None) is not valid')
1244
if NULL_REVISION in keys:
1245
keys.discard(NULL_REVISION)
1246
found_parents = {NULL_REVISION:()}
1248
return found_parents
1251
# TODO(Needs analysis): We could assume that the keys being requested
1252
# from get_parent_map are in a breadth first search, so typically they
1253
# will all be depth N from some common parent, and we don't have to
1254
# have the server iterate from the root parent, but rather from the
1255
# keys we're searching; and just tell the server the keyspace we
1256
# already have; but this may be more traffic again.
1258
# Transform self._parents_map into a search request recipe.
1259
# TODO: Manage this incrementally to avoid covering the same path
1260
# repeatedly. (The server will have to on each request, but the less
1261
# work done the better).
1263
# Negative caching notes:
1264
# new server sends missing when a request including the revid
1265
# 'include-missing:' is present in the request.
1266
# missing keys are serialised as missing:X, and we then call
1267
# provider.note_missing(X) for-all X
1268
parents_map = self._unstacked_provider.get_cached_map()
1269
if parents_map is None:
1270
# Repository is not locked, so there's no cache.
1272
# start_set is all the keys in the cache
1273
start_set = set(parents_map)
1274
# result set is all the references to keys in the cache
1275
result_parents = set()
1276
for parents in parents_map.itervalues():
1277
result_parents.update(parents)
1278
stop_keys = result_parents.difference(start_set)
1279
# We don't need to send ghosts back to the server as a position to
1281
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1282
key_count = len(parents_map)
1283
if (NULL_REVISION in result_parents
1284
and NULL_REVISION in self._unstacked_provider.missing_keys):
1285
# If we pruned NULL_REVISION from the stop_keys because it's also
1286
# in our cache of "missing" keys we need to increment our key count
1287
# by 1, because the reconsitituted SearchResult on the server will
1288
# still consider NULL_REVISION to be an included key.
1290
included_keys = start_set.intersection(result_parents)
1291
start_set.difference_update(included_keys)
1292
recipe = ('manual', start_set, stop_keys, key_count)
1293
body = self._serialise_search_recipe(recipe)
1294
path = self.bzrdir._path_for_remote_call(self._client)
1296
if type(key) is not str:
1298
"key %r not a plain string" % (key,))
1299
verb = 'Repository.get_parent_map'
1300
args = (path, 'include-missing:') + tuple(keys)
1302
response = self._call_with_body_bytes_expecting_body(
1304
except errors.UnknownSmartMethod:
1305
# Server does not support this method, so get the whole graph.
1306
# Worse, we have to force a disconnection, because the server now
1307
# doesn't realise it has a body on the wire to consume, so the
1308
# only way to recover is to abandon the connection.
1310
'Server is too old for fast get_parent_map, reconnecting. '
1311
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1313
# To avoid having to disconnect repeatedly, we keep track of the
1314
# fact the server doesn't understand remote methods added in 1.2.
1315
medium._remember_remote_is_before((1, 2))
1316
# Recurse just once and we should use the fallback code.
1317
return self._get_parent_map_rpc(keys)
1318
response_tuple, response_handler = response
1319
if response_tuple[0] not in ['ok']:
1320
response_handler.cancel_read_body()
1321
raise errors.UnexpectedSmartServerResponse(response_tuple)
1322
if response_tuple[0] == 'ok':
1323
coded = bz2.decompress(response_handler.read_body_bytes())
1325
# no revisions found
1327
lines = coded.split('\n')
1330
d = tuple(line.split())
1332
revision_graph[d[0]] = d[1:]
1335
if d[0].startswith('missing:'):
1337
self._unstacked_provider.note_missing_key(revid)
1339
# no parents - so give the Graph result
1341
revision_graph[d[0]] = (NULL_REVISION,)
1342
return revision_graph
1345
def get_signature_text(self, revision_id):
1347
return self._real_repository.get_signature_text(revision_id)
1350
def get_inventory_xml(self, revision_id):
1352
return self._real_repository.get_inventory_xml(revision_id)
1354
def deserialise_inventory(self, revision_id, xml):
1356
return self._real_repository.deserialise_inventory(revision_id, xml)
1358
def reconcile(self, other=None, thorough=False):
1360
return self._real_repository.reconcile(other=other, thorough=thorough)
1362
def all_revision_ids(self):
1364
return self._real_repository.all_revision_ids()
1367
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1369
return self._real_repository.get_deltas_for_revisions(revisions,
1370
specific_fileids=specific_fileids)
1373
def get_revision_delta(self, revision_id, specific_fileids=None):
1375
return self._real_repository.get_revision_delta(revision_id,
1376
specific_fileids=specific_fileids)
1379
def revision_trees(self, revision_ids):
1381
return self._real_repository.revision_trees(revision_ids)
1384
def get_revision_reconcile(self, revision_id):
1386
return self._real_repository.get_revision_reconcile(revision_id)
1389
def check(self, revision_ids=None):
1391
return self._real_repository.check(revision_ids=revision_ids)
1393
def copy_content_into(self, destination, revision_id=None):
1395
return self._real_repository.copy_content_into(
1396
destination, revision_id=revision_id)
1398
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1399
# get a tarball of the remote repository, and copy from that into the
1401
from bzrlib import osutils
1403
# TODO: Maybe a progress bar while streaming the tarball?
1404
note("Copying repository content as tarball...")
1405
tar_file = self._get_tarball('bz2')
1406
if tar_file is None:
1408
destination = to_bzrdir.create_repository()
1410
tar = tarfile.open('repository', fileobj=tar_file,
1412
tmpdir = osutils.mkdtemp()
1414
_extract_tar(tar, tmpdir)
1415
tmp_bzrdir = BzrDir.open(tmpdir)
1416
tmp_repo = tmp_bzrdir.open_repository()
1417
tmp_repo.copy_content_into(destination, revision_id)
1419
osutils.rmtree(tmpdir)
1423
# TODO: Suggestion from john: using external tar is much faster than
1424
# python's tarfile library, but it may not work on windows.
1427
def inventories(self):
1428
"""Decorate the real repository for now.
1430
In the long term a full blown network facility is needed to
1431
avoid creating a real repository object locally.
1434
return self._real_repository.inventories
1438
"""Compress the data within the repository.
1440
This is not currently implemented within the smart server.
1443
return self._real_repository.pack()
1446
def revisions(self):
1447
"""Decorate the real repository for now.
1449
In the short term this should become a real object to intercept graph
1452
In the long term a full blown network facility is needed.
1455
return self._real_repository.revisions
1457
def set_make_working_trees(self, new_value):
1459
new_value_str = "True"
1461
new_value_str = "False"
1462
path = self.bzrdir._path_for_remote_call(self._client)
1464
response = self._call(
1465
'Repository.set_make_working_trees', path, new_value_str)
1466
except errors.UnknownSmartMethod:
1468
self._real_repository.set_make_working_trees(new_value)
1470
if response[0] != 'ok':
1471
raise errors.UnexpectedSmartServerResponse(response)
1474
def signatures(self):
1475
"""Decorate the real repository for now.
1477
In the long term a full blown network facility is needed to avoid
1478
creating a real repository object locally.
1481
return self._real_repository.signatures
1484
def sign_revision(self, revision_id, gpg_strategy):
1486
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1490
"""Decorate the real repository for now.
1492
In the long term a full blown network facility is needed to avoid
1493
creating a real repository object locally.
1496
return self._real_repository.texts
1499
def get_revisions(self, revision_ids):
1501
return self._real_repository.get_revisions(revision_ids)
1503
def supports_rich_root(self):
1504
return self._format.rich_root_data
1506
def iter_reverse_revision_history(self, revision_id):
1508
return self._real_repository.iter_reverse_revision_history(revision_id)
1511
def _serializer(self):
1512
return self._format._serializer
1514
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1516
return self._real_repository.store_revision_signature(
1517
gpg_strategy, plaintext, revision_id)
1519
def add_signature_text(self, revision_id, signature):
1521
return self._real_repository.add_signature_text(revision_id, signature)
1523
def has_signature_for_revision_id(self, revision_id):
1525
return self._real_repository.has_signature_for_revision_id(revision_id)
1527
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1529
return self._real_repository.item_keys_introduced_by(revision_ids,
1530
_files_pb=_files_pb)
1532
def revision_graph_can_have_wrong_parents(self):
1533
# The answer depends on the remote repo format.
1535
return self._real_repository.revision_graph_can_have_wrong_parents()
1537
def _find_inconsistent_revision_parents(self):
1539
return self._real_repository._find_inconsistent_revision_parents()
1541
def _check_for_inconsistent_revision_parents(self):
1543
return self._real_repository._check_for_inconsistent_revision_parents()
1545
def _make_parents_provider(self, other=None):
1546
providers = [self._unstacked_provider]
1547
if other is not None:
1548
providers.insert(0, other)
1549
providers.extend(r._make_parents_provider() for r in
1550
self._fallback_repositories)
1551
return graph._StackedParentsProvider(providers)
1553
def _serialise_search_recipe(self, recipe):
1554
"""Serialise a graph search recipe.
1556
:param recipe: A search recipe (start, stop, count).
1557
:return: Serialised bytes.
1559
start_keys = ' '.join(recipe[1])
1560
stop_keys = ' '.join(recipe[2])
1561
count = str(recipe[3])
1562
return '\n'.join((start_keys, stop_keys, count))
1564
def _serialise_search_result(self, search_result):
1565
if isinstance(search_result, graph.PendingAncestryResult):
1566
parts = ['ancestry-of']
1567
parts.extend(search_result.heads)
1569
recipe = search_result.get_recipe()
1570
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1571
return '\n'.join(parts)
1574
path = self.bzrdir._path_for_remote_call(self._client)
1576
response = self._call('PackRepository.autopack', path)
1577
except errors.UnknownSmartMethod:
1579
self._real_repository._pack_collection.autopack()
1582
if response[0] != 'ok':
1583
raise errors.UnexpectedSmartServerResponse(response)
1586
class RemoteStreamSink(repository.StreamSink):
1588
def _insert_real(self, stream, src_format, resume_tokens):
1589
self.target_repo._ensure_real()
1590
sink = self.target_repo._real_repository._get_sink()
1591
result = sink.insert_stream(stream, src_format, resume_tokens)
1593
self.target_repo.autopack()
1596
def insert_stream(self, stream, src_format, resume_tokens):
1597
target = self.target_repo
1598
if target._lock_token:
1599
verb = 'Repository.insert_stream_locked'
1600
extra_args = (target._lock_token or '',)
1601
required_version = (1, 14)
1603
verb = 'Repository.insert_stream'
1605
required_version = (1, 13)
1606
client = target._client
1607
medium = client._medium
1608
if medium._is_remote_before(required_version):
1609
# No possible way this can work.
1610
return self._insert_real(stream, src_format, resume_tokens)
1611
path = target.bzrdir._path_for_remote_call(client)
1612
if not resume_tokens:
1613
# XXX: Ugly but important for correctness, *will* be fixed during
1614
# 1.13 cycle. Pushing a stream that is interrupted results in a
1615
# fallback to the _real_repositories sink *with a partial stream*.
1616
# Thats bad because we insert less data than bzr expected. To avoid
1617
# this we do a trial push to make sure the verb is accessible, and
1618
# do not fallback when actually pushing the stream. A cleanup patch
1619
# is going to look at rewinding/restarting the stream/partial
1621
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1623
response = client.call_with_body_stream(
1624
(verb, path, '') + extra_args, byte_stream)
1625
except errors.UnknownSmartMethod:
1626
medium._remember_remote_is_before(required_version)
1627
return self._insert_real(stream, src_format, resume_tokens)
1628
byte_stream = smart_repo._stream_to_byte_stream(
1630
resume_tokens = ' '.join(resume_tokens)
1631
response = client.call_with_body_stream(
1632
(verb, path, resume_tokens) + extra_args, byte_stream)
1633
if response[0][0] not in ('ok', 'missing-basis'):
1634
raise errors.UnexpectedSmartServerResponse(response)
1635
if response[0][0] == 'missing-basis':
1636
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1637
resume_tokens = tokens
1638
return resume_tokens, missing_keys
1640
self.target_repo.refresh_data()
1644
class RemoteStreamSource(repository.StreamSource):
1645
"""Stream data from a remote server."""
1647
def get_stream(self, search):
1648
if (self.from_repository._fallback_repositories and
1649
self.to_format._fetch_order == 'topological'):
1650
return self._real_stream(self.from_repository, search)
1651
return self.missing_parents_chain(search, [self.from_repository] +
1652
self.from_repository._fallback_repositories)
1654
def _real_stream(self, repo, search):
1655
"""Get a stream for search from repo.
1657
This never called RemoteStreamSource.get_stream, and is a heler
1658
for RemoteStreamSource._get_stream to allow getting a stream
1659
reliably whether fallback back because of old servers or trying
1660
to stream from a non-RemoteRepository (which the stacked support
1663
source = repo._get_source(self.to_format)
1664
if isinstance(source, RemoteStreamSource):
1665
return repository.StreamSource.get_stream(source, search)
1666
return source.get_stream(search)
1668
def _get_stream(self, repo, search):
1669
"""Core worker to get a stream from repo for search.
1671
This is used by both get_stream and the stacking support logic. It
1672
deliberately gets a stream for repo which does not need to be
1673
self.from_repository. In the event that repo is not Remote, or
1674
cannot do a smart stream, a fallback is made to the generic
1675
repository._get_stream() interface, via self._real_stream.
1677
In the event of stacking, streams from _get_stream will not
1678
contain all the data for search - this is normal (see get_stream).
1680
:param repo: A repository.
1681
:param search: A search.
1683
# Fallbacks may be non-smart
1684
if not isinstance(repo, RemoteRepository):
1685
return self._real_stream(repo, search)
1686
client = repo._client
1687
medium = client._medium
1688
if medium._is_remote_before((1, 13)):
1689
# streaming was added in 1.13
1690
return self._real_stream(repo, search)
1691
path = repo.bzrdir._path_for_remote_call(client)
1693
search_bytes = repo._serialise_search_result(search)
1694
response = repo._call_with_body_bytes_expecting_body(
1695
'Repository.get_stream',
1696
(path, self.to_format.network_name()), search_bytes)
1697
response_tuple, response_handler = response
1698
except errors.UnknownSmartMethod:
1699
medium._remember_remote_is_before((1,13))
1700
return self._real_stream(repo, search)
1701
if response_tuple[0] != 'ok':
1702
raise errors.UnexpectedSmartServerResponse(response_tuple)
1703
byte_stream = response_handler.read_streamed_body()
1704
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1705
if src_format.network_name() != repo._format.network_name():
1706
raise AssertionError(
1707
"Mismatched RemoteRepository and stream src %r, %r" % (
1708
src_format.network_name(), repo._format.network_name()))
1711
def missing_parents_chain(self, search, sources):
1712
"""Chain multiple streams together to handle stacking.
1714
:param search: The overall search to satisfy with streams.
1715
:param sources: A list of Repository objects to query.
1717
self.serialiser = self.to_format._serializer
1718
self.seen_revs = set()
1719
self.referenced_revs = set()
1720
# If there are heads in the search, or the key count is > 0, we are not
1722
while not search.is_empty() and len(sources) > 1:
1723
source = sources.pop(0)
1724
stream = self._get_stream(source, search)
1725
for kind, substream in stream:
1726
if kind != 'revisions':
1727
yield kind, substream
1729
yield kind, self.missing_parents_rev_handler(substream)
1730
search = search.refine(self.seen_revs, self.referenced_revs)
1731
self.seen_revs = set()
1732
self.referenced_revs = set()
1733
if not search.is_empty():
1734
for kind, stream in self._get_stream(sources[0], search):
1737
def missing_parents_rev_handler(self, substream):
1738
for content in substream:
1739
revision_bytes = content.get_bytes_as('fulltext')
1740
revision = self.serialiser.read_revision_from_string(revision_bytes)
1741
self.seen_revs.add(content.key[-1])
1742
self.referenced_revs.update(revision.parent_ids)
1746
class RemoteBranchLockableFiles(LockableFiles):
1747
"""A 'LockableFiles' implementation that talks to a smart server.
1749
This is not a public interface class.
1752
def __init__(self, bzrdir, _client):
1753
self.bzrdir = bzrdir
1754
self._client = _client
1755
self._need_find_modes = True
1756
LockableFiles.__init__(
1757
self, bzrdir.get_branch_transport(None),
1758
'lock', lockdir.LockDir)
1760
def _find_modes(self):
1761
# RemoteBranches don't let the client set the mode of control files.
1762
self._dir_mode = None
1763
self._file_mode = None
1766
class RemoteBranchFormat(branch.BranchFormat):
1768
def __init__(self, network_name=None):
1769
super(RemoteBranchFormat, self).__init__()
1770
self._matchingbzrdir = RemoteBzrDirFormat()
1771
self._matchingbzrdir.set_branch_format(self)
1772
self._custom_format = None
1773
self._network_name = network_name
1775
def __eq__(self, other):
1776
return (isinstance(other, RemoteBranchFormat) and
1777
self.__dict__ == other.__dict__)
1779
def _ensure_real(self):
1780
if self._custom_format is None:
1781
self._custom_format = branch.network_format_registry.get(
1784
def get_format_description(self):
1785
return 'Remote BZR Branch'
1787
def network_name(self):
1788
return self._network_name
1790
def open(self, a_bzrdir, ignore_fallbacks=False):
1791
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1793
def _vfs_initialize(self, a_bzrdir):
1794
# Initialisation when using a local bzrdir object, or a non-vfs init
1795
# method is not available on the server.
1796
# self._custom_format is always set - the start of initialize ensures
1798
if isinstance(a_bzrdir, RemoteBzrDir):
1799
a_bzrdir._ensure_real()
1800
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1802
# We assume the bzrdir is parameterised; it may not be.
1803
result = self._custom_format.initialize(a_bzrdir)
1804
if (isinstance(a_bzrdir, RemoteBzrDir) and
1805
not isinstance(result, RemoteBranch)):
1806
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1809
def initialize(self, a_bzrdir):
1810
# 1) get the network name to use.
1811
if self._custom_format:
1812
network_name = self._custom_format.network_name()
1814
# Select the current bzrlib default and ask for that.
1815
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1816
reference_format = reference_bzrdir_format.get_branch_format()
1817
self._custom_format = reference_format
1818
network_name = reference_format.network_name()
1819
# Being asked to create on a non RemoteBzrDir:
1820
if not isinstance(a_bzrdir, RemoteBzrDir):
1821
return self._vfs_initialize(a_bzrdir)
1822
medium = a_bzrdir._client._medium
1823
if medium._is_remote_before((1, 13)):
1824
return self._vfs_initialize(a_bzrdir)
1825
# Creating on a remote bzr dir.
1826
# 2) try direct creation via RPC
1827
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1828
verb = 'BzrDir.create_branch'
1830
response = a_bzrdir._call(verb, path, network_name)
1831
except errors.UnknownSmartMethod:
1832
# Fallback - use vfs methods
1833
medium._remember_remote_is_before((1, 13))
1834
return self._vfs_initialize(a_bzrdir)
1835
if response[0] != 'ok':
1836
raise errors.UnexpectedSmartServerResponse(response)
1837
# Turn the response into a RemoteRepository object.
1838
format = RemoteBranchFormat(network_name=response[1])
1839
repo_format = response_tuple_to_repo_format(response[3:])
1840
if response[2] == '':
1841
repo_bzrdir = a_bzrdir
1843
repo_bzrdir = RemoteBzrDir(
1844
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1846
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1847
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1848
format=format, setup_stacking=False)
1849
# XXX: We know this is a new branch, so it must have revno 0, revid
1850
# NULL_REVISION. Creating the branch locked would make this be unable
1851
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1852
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1853
return remote_branch
1855
def make_tags(self, branch):
1857
return self._custom_format.make_tags(branch)
1859
def supports_tags(self):
1860
# Remote branches might support tags, but we won't know until we
1861
# access the real remote branch.
1863
return self._custom_format.supports_tags()
1865
def supports_stacking(self):
1867
return self._custom_format.supports_stacking()
1870
class RemoteBranch(branch.Branch, _RpcHelper):
1871
"""Branch stored on a server accessed by HPSS RPC.
1873
At the moment most operations are mapped down to simple file operations.
1876
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1877
_client=None, format=None, setup_stacking=True):
1878
"""Create a RemoteBranch instance.
1880
:param real_branch: An optional local implementation of the branch
1881
format, usually accessing the data via the VFS.
1882
:param _client: Private parameter for testing.
1883
:param format: A RemoteBranchFormat object, None to create one
1884
automatically. If supplied it should have a network_name already
1886
:param setup_stacking: If True make an RPC call to determine the
1887
stacked (or not) status of the branch. If False assume the branch
1890
# We intentionally don't call the parent class's __init__, because it
1891
# will try to assign to self.tags, which is a property in this subclass.
1892
# And the parent's __init__ doesn't do much anyway.
1893
self._revision_id_to_revno_cache = None
1894
self._partial_revision_id_to_revno_cache = {}
1895
self._revision_history_cache = None
1896
self._last_revision_info_cache = None
1897
self._merge_sorted_revisions_cache = None
1898
self.bzrdir = remote_bzrdir
1899
if _client is not None:
1900
self._client = _client
1902
self._client = remote_bzrdir._client
1903
self.repository = remote_repository
1904
if real_branch is not None:
1905
self._real_branch = real_branch
1906
# Give the remote repository the matching real repo.
1907
real_repo = self._real_branch.repository
1908
if isinstance(real_repo, RemoteRepository):
1909
real_repo._ensure_real()
1910
real_repo = real_repo._real_repository
1911
self.repository._set_real_repository(real_repo)
1912
# Give the branch the remote repository to let fast-pathing happen.
1913
self._real_branch.repository = self.repository
1915
self._real_branch = None
1916
# Fill out expected attributes of branch for bzrlib api users.
1917
self.base = self.bzrdir.root_transport.base
1918
self._control_files = None
1919
self._lock_mode = None
1920
self._lock_token = None
1921
self._repo_lock_token = None
1922
self._lock_count = 0
1923
self._leave_lock = False
1924
# Setup a format: note that we cannot call _ensure_real until all the
1925
# attributes above are set: This code cannot be moved higher up in this
1928
self._format = RemoteBranchFormat()
1929
if real_branch is not None:
1930
self._format._network_name = \
1931
self._real_branch._format.network_name()
1933
self._format = format
1934
if not self._format._network_name:
1935
# Did not get from open_branchV2 - old server.
1937
self._format._network_name = \
1938
self._real_branch._format.network_name()
1939
self.tags = self._format.make_tags(self)
1940
# The base class init is not called, so we duplicate this:
1941
hooks = branch.Branch.hooks['open']
1945
self._setup_stacking()
1947
def _setup_stacking(self):
1948
# configure stacking into the remote repository, by reading it from
1951
fallback_url = self.get_stacked_on_url()
1952
except (errors.NotStacked, errors.UnstackableBranchFormat,
1953
errors.UnstackableRepositoryFormat), e:
1955
self._activate_fallback_location(fallback_url)
1957
def _get_config(self):
1958
return RemoteBranchConfig(self)
1960
def _get_real_transport(self):
1961
# if we try vfs access, return the real branch's vfs transport
1963
return self._real_branch._transport
1965
_transport = property(_get_real_transport)
1968
return "%s(%s)" % (self.__class__.__name__, self.base)
1972
def _ensure_real(self):
1973
"""Ensure that there is a _real_branch set.
1975
Used before calls to self._real_branch.
1977
if self._real_branch is None:
1978
if not vfs.vfs_enabled():
1979
raise AssertionError('smart server vfs must be enabled '
1980
'to use vfs implementation')
1981
self.bzrdir._ensure_real()
1982
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1983
if self.repository._real_repository is None:
1984
# Give the remote repository the matching real repo.
1985
real_repo = self._real_branch.repository
1986
if isinstance(real_repo, RemoteRepository):
1987
real_repo._ensure_real()
1988
real_repo = real_repo._real_repository
1989
self.repository._set_real_repository(real_repo)
1990
# Give the real branch the remote repository to let fast-pathing
1992
self._real_branch.repository = self.repository
1993
if self._lock_mode == 'r':
1994
self._real_branch.lock_read()
1995
elif self._lock_mode == 'w':
1996
self._real_branch.lock_write(token=self._lock_token)
1998
def _translate_error(self, err, **context):
1999
self.repository._translate_error(err, branch=self, **context)
2001
def _clear_cached_state(self):
2002
super(RemoteBranch, self)._clear_cached_state()
2003
if self._real_branch is not None:
2004
self._real_branch._clear_cached_state()
2006
def _clear_cached_state_of_remote_branch_only(self):
2007
"""Like _clear_cached_state, but doesn't clear the cache of
2010
This is useful when falling back to calling a method of
2011
self._real_branch that changes state. In that case the underlying
2012
branch changes, so we need to invalidate this RemoteBranch's cache of
2013
it. However, there's no need to invalidate the _real_branch's cache
2014
too, in fact doing so might harm performance.
2016
super(RemoteBranch, self)._clear_cached_state()
2019
def control_files(self):
2020
# Defer actually creating RemoteBranchLockableFiles until its needed,
2021
# because it triggers an _ensure_real that we otherwise might not need.
2022
if self._control_files is None:
2023
self._control_files = RemoteBranchLockableFiles(
2024
self.bzrdir, self._client)
2025
return self._control_files
2027
def _get_checkout_format(self):
2029
return self._real_branch._get_checkout_format()
2031
def get_physical_lock_status(self):
2032
"""See Branch.get_physical_lock_status()."""
2033
# should be an API call to the server, as branches must be lockable.
2035
return self._real_branch.get_physical_lock_status()
2037
def get_stacked_on_url(self):
2038
"""Get the URL this branch is stacked against.
2040
:raises NotStacked: If the branch is not stacked.
2041
:raises UnstackableBranchFormat: If the branch does not support
2043
:raises UnstackableRepositoryFormat: If the repository does not support
2047
# there may not be a repository yet, so we can't use
2048
# self._translate_error, so we can't use self._call either.
2049
response = self._client.call('Branch.get_stacked_on_url',
2050
self._remote_path())
2051
except errors.ErrorFromSmartServer, err:
2052
# there may not be a repository yet, so we can't call through
2053
# its _translate_error
2054
_translate_error(err, branch=self)
2055
except errors.UnknownSmartMethod, err:
2057
return self._real_branch.get_stacked_on_url()
2058
if response[0] != 'ok':
2059
raise errors.UnexpectedSmartServerResponse(response)
2062
def _vfs_get_tags_bytes(self):
2064
return self._real_branch._get_tags_bytes()
2066
def _get_tags_bytes(self):
2067
medium = self._client._medium
2068
if medium._is_remote_before((1, 13)):
2069
return self._vfs_get_tags_bytes()
2071
response = self._call('Branch.get_tags_bytes', self._remote_path())
2072
except errors.UnknownSmartMethod:
2073
medium._remember_remote_is_before((1, 13))
2074
return self._vfs_get_tags_bytes()
2077
def lock_read(self):
2078
self.repository.lock_read()
2079
if not self._lock_mode:
2080
self._lock_mode = 'r'
2081
self._lock_count = 1
2082
if self._real_branch is not None:
2083
self._real_branch.lock_read()
2085
self._lock_count += 1
2087
def _remote_lock_write(self, token):
2089
branch_token = repo_token = ''
2091
branch_token = token
2092
repo_token = self.repository.lock_write()
2093
self.repository.unlock()
2094
err_context = {'token': token}
2095
response = self._call(
2096
'Branch.lock_write', self._remote_path(), branch_token,
2097
repo_token or '', **err_context)
2098
if response[0] != 'ok':
2099
raise errors.UnexpectedSmartServerResponse(response)
2100
ok, branch_token, repo_token = response
2101
return branch_token, repo_token
2103
def lock_write(self, token=None):
2104
if not self._lock_mode:
2105
# Lock the branch and repo in one remote call.
2106
remote_tokens = self._remote_lock_write(token)
2107
self._lock_token, self._repo_lock_token = remote_tokens
2108
if not self._lock_token:
2109
raise SmartProtocolError('Remote server did not return a token!')
2110
# Tell the self.repository object that it is locked.
2111
self.repository.lock_write(
2112
self._repo_lock_token, _skip_rpc=True)
2114
if self._real_branch is not None:
2115
self._real_branch.lock_write(token=self._lock_token)
2116
if token is not None:
2117
self._leave_lock = True
2119
self._leave_lock = False
2120
self._lock_mode = 'w'
2121
self._lock_count = 1
2122
elif self._lock_mode == 'r':
2123
raise errors.ReadOnlyTransaction
2125
if token is not None:
2126
# A token was given to lock_write, and we're relocking, so
2127
# check that the given token actually matches the one we
2129
if token != self._lock_token:
2130
raise errors.TokenMismatch(token, self._lock_token)
2131
self._lock_count += 1
2132
# Re-lock the repository too.
2133
self.repository.lock_write(self._repo_lock_token)
2134
return self._lock_token or None
2136
def _set_tags_bytes(self, bytes):
2138
return self._real_branch._set_tags_bytes(bytes)
2140
def _unlock(self, branch_token, repo_token):
2141
err_context = {'token': str((branch_token, repo_token))}
2142
response = self._call(
2143
'Branch.unlock', self._remote_path(), branch_token,
2144
repo_token or '', **err_context)
2145
if response == ('ok',):
2147
raise errors.UnexpectedSmartServerResponse(response)
2151
self._lock_count -= 1
2152
if not self._lock_count:
2153
self._clear_cached_state()
2154
mode = self._lock_mode
2155
self._lock_mode = None
2156
if self._real_branch is not None:
2157
if (not self._leave_lock and mode == 'w' and
2158
self._repo_lock_token):
2159
# If this RemoteBranch will remove the physical lock
2160
# for the repository, make sure the _real_branch
2161
# doesn't do it first. (Because the _real_branch's
2162
# repository is set to be the RemoteRepository.)
2163
self._real_branch.repository.leave_lock_in_place()
2164
self._real_branch.unlock()
2166
# Only write-locked branched need to make a remote method
2167
# call to perfom the unlock.
2169
if not self._lock_token:
2170
raise AssertionError('Locked, but no token!')
2171
branch_token = self._lock_token
2172
repo_token = self._repo_lock_token
2173
self._lock_token = None
2174
self._repo_lock_token = None
2175
if not self._leave_lock:
2176
self._unlock(branch_token, repo_token)
2178
self.repository.unlock()
2180
def break_lock(self):
2182
return self._real_branch.break_lock()
2184
def leave_lock_in_place(self):
2185
if not self._lock_token:
2186
raise NotImplementedError(self.leave_lock_in_place)
2187
self._leave_lock = True
2189
def dont_leave_lock_in_place(self):
2190
if not self._lock_token:
2191
raise NotImplementedError(self.dont_leave_lock_in_place)
2192
self._leave_lock = False
2194
def _last_revision_info(self):
2195
response = self._call('Branch.last_revision_info', self._remote_path())
2196
if response[0] != 'ok':
2197
raise SmartProtocolError('unexpected response code %s' % (response,))
2198
revno = int(response[1])
2199
last_revision = response[2]
2200
return (revno, last_revision)
2202
def _gen_revision_history(self):
2203
"""See Branch._gen_revision_history()."""
2204
response_tuple, response_handler = self._call_expecting_body(
2205
'Branch.revision_history', self._remote_path())
2206
if response_tuple[0] != 'ok':
2207
raise errors.UnexpectedSmartServerResponse(response_tuple)
2208
result = response_handler.read_body_bytes().split('\x00')
2213
def _remote_path(self):
2214
return self.bzrdir._path_for_remote_call(self._client)
2216
def _set_last_revision_descendant(self, revision_id, other_branch,
2217
allow_diverged=False, allow_overwrite_descendant=False):
2218
# This performs additional work to meet the hook contract; while its
2219
# undesirable, we have to synthesise the revno to call the hook, and
2220
# not calling the hook is worse as it means changes can't be prevented.
2221
# Having calculated this though, we can't just call into
2222
# set_last_revision_info as a simple call, because there is a set_rh
2223
# hook that some folk may still be using.
2224
old_revno, old_revid = self.last_revision_info()
2225
history = self._lefthand_history(revision_id)
2226
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2227
err_context = {'other_branch': other_branch}
2228
response = self._call('Branch.set_last_revision_ex',
2229
self._remote_path(), self._lock_token, self._repo_lock_token,
2230
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2232
self._clear_cached_state()
2233
if len(response) != 3 and response[0] != 'ok':
2234
raise errors.UnexpectedSmartServerResponse(response)
2235
new_revno, new_revision_id = response[1:]
2236
self._last_revision_info_cache = new_revno, new_revision_id
2237
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2238
if self._real_branch is not None:
2239
cache = new_revno, new_revision_id
2240
self._real_branch._last_revision_info_cache = cache
2242
def _set_last_revision(self, revision_id):
2243
old_revno, old_revid = self.last_revision_info()
2244
# This performs additional work to meet the hook contract; while its
2245
# undesirable, we have to synthesise the revno to call the hook, and
2246
# not calling the hook is worse as it means changes can't be prevented.
2247
# Having calculated this though, we can't just call into
2248
# set_last_revision_info as a simple call, because there is a set_rh
2249
# hook that some folk may still be using.
2250
history = self._lefthand_history(revision_id)
2251
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2252
self._clear_cached_state()
2253
response = self._call('Branch.set_last_revision',
2254
self._remote_path(), self._lock_token, self._repo_lock_token,
2256
if response != ('ok',):
2257
raise errors.UnexpectedSmartServerResponse(response)
2258
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2261
def set_revision_history(self, rev_history):
2262
# Send just the tip revision of the history; the server will generate
2263
# the full history from that. If the revision doesn't exist in this
2264
# branch, NoSuchRevision will be raised.
2265
if rev_history == []:
2268
rev_id = rev_history[-1]
2269
self._set_last_revision(rev_id)
2270
for hook in branch.Branch.hooks['set_rh']:
2271
hook(self, rev_history)
2272
self._cache_revision_history(rev_history)
2274
def _get_parent_location(self):
2275
medium = self._client._medium
2276
if medium._is_remote_before((1, 13)):
2277
return self._vfs_get_parent_location()
2279
response = self._call('Branch.get_parent', self._remote_path())
2280
except errors.UnknownSmartMethod:
2281
medium._remember_remote_is_before((1, 13))
2282
return self._vfs_get_parent_location()
2283
if len(response) != 1:
2284
raise errors.UnexpectedSmartServerResponse(response)
2285
parent_location = response[0]
2286
if parent_location == '':
2288
return parent_location
2290
def _vfs_get_parent_location(self):
2292
return self._real_branch._get_parent_location()
2294
def set_parent(self, url):
2296
return self._real_branch.set_parent(url)
2298
def _set_parent_location(self, url):
2299
# Used by tests, to poke bad urls into branch configurations
2301
self.set_parent(url)
2304
return self._real_branch._set_parent_location(url)
2307
def pull(self, source, overwrite=False, stop_revision=None,
2309
self._clear_cached_state_of_remote_branch_only()
2311
return self._real_branch.pull(
2312
source, overwrite=overwrite, stop_revision=stop_revision,
2313
_override_hook_target=self, **kwargs)
2316
def push(self, target, overwrite=False, stop_revision=None):
2318
return self._real_branch.push(
2319
target, overwrite=overwrite, stop_revision=stop_revision,
2320
_override_hook_source_branch=self)
2322
def is_locked(self):
2323
return self._lock_count >= 1
2326
def revision_id_to_revno(self, revision_id):
2328
return self._real_branch.revision_id_to_revno(revision_id)
2331
def set_last_revision_info(self, revno, revision_id):
2332
# XXX: These should be returned by the set_last_revision_info verb
2333
old_revno, old_revid = self.last_revision_info()
2334
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2335
revision_id = ensure_null(revision_id)
2337
response = self._call('Branch.set_last_revision_info',
2338
self._remote_path(), self._lock_token, self._repo_lock_token,
2339
str(revno), revision_id)
2340
except errors.UnknownSmartMethod:
2342
self._clear_cached_state_of_remote_branch_only()
2343
self._real_branch.set_last_revision_info(revno, revision_id)
2344
self._last_revision_info_cache = revno, revision_id
2346
if response == ('ok',):
2347
self._clear_cached_state()
2348
self._last_revision_info_cache = revno, revision_id
2349
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2350
# Update the _real_branch's cache too.
2351
if self._real_branch is not None:
2352
cache = self._last_revision_info_cache
2353
self._real_branch._last_revision_info_cache = cache
2355
raise errors.UnexpectedSmartServerResponse(response)
2358
def generate_revision_history(self, revision_id, last_rev=None,
2360
medium = self._client._medium
2361
if not medium._is_remote_before((1, 6)):
2362
# Use a smart method for 1.6 and above servers
2364
self._set_last_revision_descendant(revision_id, other_branch,
2365
allow_diverged=True, allow_overwrite_descendant=True)
2367
except errors.UnknownSmartMethod:
2368
medium._remember_remote_is_before((1, 6))
2369
self._clear_cached_state_of_remote_branch_only()
2370
self.set_revision_history(self._lefthand_history(revision_id,
2371
last_rev=last_rev,other_branch=other_branch))
2373
def set_push_location(self, location):
2375
return self._real_branch.set_push_location(location)
2378
class RemoteBranchConfig(object):
2379
"""A Config that reads from a smart branch and writes via smart methods.
2381
It is a low-level object that considers config data to be name/value pairs
2382
that may be associated with a section. Assigning meaning to the these
2383
values is done at higher levels like bzrlib.config.TreeConfig.
2386
def __init__(self, branch):
2387
self._branch = branch
2389
def get_option(self, name, section=None, default=None):
2390
"""Return the value associated with a named option.
2392
:param name: The name of the value
2393
:param section: The section the option is in (if any)
2394
:param default: The value to return if the value is not set
2395
:return: The value or default value
2397
configobj = self._get_configobj()
2399
section_obj = configobj
2402
section_obj = configobj[section]
2405
return section_obj.get(name, default)
2407
def _get_configobj(self):
2408
path = self._branch._remote_path()
2409
response = self._branch._client.call_expecting_body(
2410
'Branch.get_config_file', path)
2411
if response[0][0] != 'ok':
2412
raise UnexpectedSmartServerResponse(response)
2413
lines = response[1].read_body_bytes().splitlines()
2414
return config.ConfigObj(lines, encoding='utf-8')
2416
def set_option(self, value, name, section=None):
2417
"""Set the value associated with a named option.
2419
:param value: The value to set
2420
:param name: The name of the value to set
2421
:param section: The section the option is in (if any)
2423
medium = self._branch._client._medium
2424
if medium._is_remote_before((1, 14)):
2425
return self._vfs_set_option(value, name, section)
2427
path = self._branch._remote_path()
2428
response = self._branch._client.call('Branch.set_config_option',
2429
path, self._branch._lock_token, self._branch._repo_lock_token,
2430
value.encode('utf8'), name, section or '')
2431
except errors.UnknownSmartMethod:
2432
medium._remember_remote_is_before((1, 14))
2433
return self._vfs_set_option(value, name, section)
2435
raise errors.UnexpectedSmartServerResponse(response)
2437
def _vfs_set_option(self, value, name, section=None):
2438
self._branch._ensure_real()
2439
return self._branch._real_branch._get_config().set_option(
2440
value, name, section)
2443
def _extract_tar(tar, to_dir):
2444
"""Extract all the contents of a tarfile object.
2446
A replacement for extractall, which is not present in python2.4
2449
tar.extract(tarinfo, to_dir)
2452
def _translate_error(err, **context):
2453
"""Translate an ErrorFromSmartServer into a more useful error.
2455
Possible context keys:
2463
If the error from the server doesn't match a known pattern, then
2464
UnknownErrorFromSmartServer is raised.
2468
return context[name]
2469
except KeyError, key_err:
2470
mutter('Missing key %r in context %r', key_err.args[0], context)
2473
"""Get the path from the context if present, otherwise use first error
2477
return context['path']
2478
except KeyError, key_err:
2480
return err.error_args[0]
2481
except IndexError, idx_err:
2483
'Missing key %r in context %r', key_err.args[0], context)
2486
if err.error_verb == 'NoSuchRevision':
2487
raise NoSuchRevision(find('branch'), err.error_args[0])
2488
elif err.error_verb == 'nosuchrevision':
2489
raise NoSuchRevision(find('repository'), err.error_args[0])
2490
elif err.error_tuple == ('nobranch',):
2491
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2492
elif err.error_verb == 'norepository':
2493
raise errors.NoRepositoryPresent(find('bzrdir'))
2494
elif err.error_verb == 'LockContention':
2495
raise errors.LockContention('(remote lock)')
2496
elif err.error_verb == 'UnlockableTransport':
2497
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2498
elif err.error_verb == 'LockFailed':
2499
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2500
elif err.error_verb == 'TokenMismatch':
2501
raise errors.TokenMismatch(find('token'), '(remote token)')
2502
elif err.error_verb == 'Diverged':
2503
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2504
elif err.error_verb == 'TipChangeRejected':
2505
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2506
elif err.error_verb == 'UnstackableBranchFormat':
2507
raise errors.UnstackableBranchFormat(*err.error_args)
2508
elif err.error_verb == 'UnstackableRepositoryFormat':
2509
raise errors.UnstackableRepositoryFormat(*err.error_args)
2510
elif err.error_verb == 'NotStacked':
2511
raise errors.NotStacked(branch=find('branch'))
2512
elif err.error_verb == 'PermissionDenied':
2514
if len(err.error_args) >= 2:
2515
extra = err.error_args[1]
2518
raise errors.PermissionDenied(path, extra=extra)
2519
elif err.error_verb == 'ReadError':
2521
raise errors.ReadError(path)
2522
elif err.error_verb == 'NoSuchFile':
2524
raise errors.NoSuchFile(path)
2525
elif err.error_verb == 'FileExists':
2526
raise errors.FileExists(err.error_args[0])
2527
elif err.error_verb == 'DirectoryNotEmpty':
2528
raise errors.DirectoryNotEmpty(err.error_args[0])
2529
elif err.error_verb == 'ShortReadvError':
2530
args = err.error_args
2531
raise errors.ShortReadvError(
2532
args[0], int(args[1]), int(args[2]), int(args[3]))
2533
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2534
encoding = str(err.error_args[0]) # encoding must always be a string
2535
val = err.error_args[1]
2536
start = int(err.error_args[2])
2537
end = int(err.error_args[3])
2538
reason = str(err.error_args[4]) # reason must always be a string
2539
if val.startswith('u:'):
2540
val = val[2:].decode('utf-8')
2541
elif val.startswith('s:'):
2542
val = val[2:].decode('base64')
2543
if err.error_verb == 'UnicodeDecodeError':
2544
raise UnicodeDecodeError(encoding, val, start, end, reason)
2545
elif err.error_verb == 'UnicodeEncodeError':
2546
raise UnicodeEncodeError(encoding, val, start, end, reason)
2547
elif err.error_verb == 'ReadOnlyError':
2548
raise errors.TransportNotPossible('readonly transport')
2549
raise errors.UnknownErrorFromSmartServer(err)