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
# If the _real_repository has _fallback_repositories, clear them out,
921
# because we want it to have the same set as this repository. This is
922
# reasonable to do because the fallbacks we clear here are from a
923
# "real" branch, and we're about to replace them with the equivalents
924
# from a RemoteBranch.
925
self._real_repository._fallback_repositories = []
926
for fb in self._fallback_repositories:
927
self._real_repository.add_fallback_repository(fb)
928
if self._lock_mode == 'w':
929
# if we are already locked, the real repository must be able to
930
# acquire the lock with our token.
931
self._real_repository.lock_write(self._lock_token)
932
elif self._lock_mode == 'r':
933
self._real_repository.lock_read()
935
def start_write_group(self):
936
"""Start a write group on the decorated repository.
938
Smart methods peform operations in a single step so this api
939
is not really applicable except as a compatibility thunk
940
for older plugins that don't use e.g. the CommitBuilder
944
return self._real_repository.start_write_group()
946
def _unlock(self, token):
947
path = self.bzrdir._path_for_remote_call(self._client)
949
# with no token the remote repository is not persistently locked.
951
err_context = {'token': token}
952
response = self._call('Repository.unlock', path, token,
954
if response == ('ok',):
957
raise errors.UnexpectedSmartServerResponse(response)
960
if not self._lock_count:
961
raise errors.LockNotHeld(self)
962
self._lock_count -= 1
963
if self._lock_count > 0:
965
self._unstacked_provider.disable_cache()
966
old_mode = self._lock_mode
967
self._lock_mode = None
969
# The real repository is responsible at present for raising an
970
# exception if it's in an unfinished write group. However, it
971
# normally will *not* actually remove the lock from disk - that's
972
# done by the server on receiving the Repository.unlock call.
973
# This is just to let the _real_repository stay up to date.
974
if self._real_repository is not None:
975
self._real_repository.unlock()
977
# The rpc-level lock should be released even if there was a
978
# problem releasing the vfs-based lock.
980
# Only write-locked repositories need to make a remote method
981
# call to perfom the unlock.
982
old_token = self._lock_token
983
self._lock_token = None
984
if not self._leave_lock:
985
self._unlock(old_token)
987
def break_lock(self):
988
# should hand off to the network
990
return self._real_repository.break_lock()
992
def _get_tarball(self, compression):
993
"""Return a TemporaryFile containing a repository tarball.
995
Returns None if the server does not support sending tarballs.
998
path = self.bzrdir._path_for_remote_call(self._client)
1000
response, protocol = self._call_expecting_body(
1001
'Repository.tarball', path, compression)
1002
except errors.UnknownSmartMethod:
1003
protocol.cancel_read_body()
1005
if response[0] == 'ok':
1006
# Extract the tarball and return it
1007
t = tempfile.NamedTemporaryFile()
1008
# TODO: rpc layer should read directly into it...
1009
t.write(protocol.read_body_bytes())
1012
raise errors.UnexpectedSmartServerResponse(response)
1014
def sprout(self, to_bzrdir, revision_id=None):
1015
# TODO: Option to control what format is created?
1017
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1019
dest_repo.fetch(self, revision_id=revision_id)
1022
### These methods are just thin shims to the VFS object for now.
1024
def revision_tree(self, revision_id):
1026
return self._real_repository.revision_tree(revision_id)
1028
def get_serializer_format(self):
1030
return self._real_repository.get_serializer_format()
1032
def get_commit_builder(self, branch, parents, config, timestamp=None,
1033
timezone=None, committer=None, revprops=None,
1035
# FIXME: It ought to be possible to call this without immediately
1036
# triggering _ensure_real. For now it's the easiest thing to do.
1038
real_repo = self._real_repository
1039
builder = real_repo.get_commit_builder(branch, parents,
1040
config, timestamp=timestamp, timezone=timezone,
1041
committer=committer, revprops=revprops, revision_id=revision_id)
1044
def add_fallback_repository(self, repository):
1045
"""Add a repository to use for looking up data not held locally.
1047
:param repository: A repository.
1049
if not self._format.supports_external_lookups:
1050
raise errors.UnstackableRepositoryFormat(
1051
self._format.network_name(), self.base)
1052
# We need to accumulate additional repositories here, to pass them in
1055
self._fallback_repositories.append(repository)
1056
# If self._real_repository was parameterised already (e.g. because a
1057
# _real_branch had its get_stacked_on_url method called), then the
1058
# repository to be added may already be in the _real_repositories list.
1059
if self._real_repository is not None:
1060
if repository not in self._real_repository._fallback_repositories:
1061
self._real_repository.add_fallback_repository(repository)
1063
def add_inventory(self, revid, inv, parents):
1065
return self._real_repository.add_inventory(revid, inv, parents)
1067
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1070
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1071
delta, new_revision_id, parents)
1073
def add_revision(self, rev_id, rev, inv=None, config=None):
1075
return self._real_repository.add_revision(
1076
rev_id, rev, inv=inv, config=config)
1079
def get_inventory(self, revision_id):
1081
return self._real_repository.get_inventory(revision_id)
1083
def iter_inventories(self, revision_ids):
1085
return self._real_repository.iter_inventories(revision_ids)
1088
def get_revision(self, revision_id):
1090
return self._real_repository.get_revision(revision_id)
1092
def get_transaction(self):
1094
return self._real_repository.get_transaction()
1097
def clone(self, a_bzrdir, revision_id=None):
1099
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1101
def make_working_trees(self):
1102
"""See Repository.make_working_trees"""
1104
return self._real_repository.make_working_trees()
1106
def refresh_data(self):
1107
"""Re-read any data needed to to synchronise with disk.
1109
This method is intended to be called after another repository instance
1110
(such as one used by a smart server) has inserted data into the
1111
repository. It may not be called during a write group, but may be
1112
called at any other time.
1114
if self.is_in_write_group():
1115
raise errors.InternalBzrError(
1116
"May not refresh_data while in a write group.")
1117
if self._real_repository is not None:
1118
self._real_repository.refresh_data()
1120
def revision_ids_to_search_result(self, result_set):
1121
"""Convert a set of revision ids to a graph SearchResult."""
1122
result_parents = set()
1123
for parents in self.get_graph().get_parent_map(
1124
result_set).itervalues():
1125
result_parents.update(parents)
1126
included_keys = result_set.intersection(result_parents)
1127
start_keys = result_set.difference(included_keys)
1128
exclude_keys = result_parents.difference(result_set)
1129
result = graph.SearchResult(start_keys, exclude_keys,
1130
len(result_set), result_set)
1134
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1135
"""Return the revision ids that other has that this does not.
1137
These are returned in topological order.
1139
revision_id: only return revision ids included by revision_id.
1141
return repository.InterRepository.get(
1142
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1144
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1146
# No base implementation to use as RemoteRepository is not a subclass
1147
# of Repository; so this is a copy of Repository.fetch().
1148
if fetch_spec is not None and revision_id is not None:
1149
raise AssertionError(
1150
"fetch_spec and revision_id are mutually exclusive.")
1151
if self.is_in_write_group():
1152
raise errors.InternalBzrError(
1153
"May not fetch while in a write group.")
1154
# fast path same-url fetch operations
1155
if self.has_same_location(source) and fetch_spec is None:
1156
# check that last_revision is in 'from' and then return a
1158
if (revision_id is not None and
1159
not revision.is_null(revision_id)):
1160
self.get_revision(revision_id)
1162
# if there is no specific appropriate InterRepository, this will get
1163
# the InterRepository base class, which raises an
1164
# IncompatibleRepositories when asked to fetch.
1165
inter = repository.InterRepository.get(source, self)
1166
return inter.fetch(revision_id=revision_id, pb=pb,
1167
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1169
def create_bundle(self, target, base, fileobj, format=None):
1171
self._real_repository.create_bundle(target, base, fileobj, format)
1174
def get_ancestry(self, revision_id, topo_sorted=True):
1176
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1178
def fileids_altered_by_revision_ids(self, revision_ids):
1180
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1182
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1184
return self._real_repository._get_versioned_file_checker(
1185
revisions, revision_versions_cache)
1187
def iter_files_bytes(self, desired_files):
1188
"""See Repository.iter_file_bytes.
1191
return self._real_repository.iter_files_bytes(desired_files)
1193
def get_parent_map(self, revision_ids):
1194
"""See bzrlib.Graph.get_parent_map()."""
1195
return self._make_parents_provider().get_parent_map(revision_ids)
1197
def _get_parent_map_rpc(self, keys):
1198
"""Helper for get_parent_map that performs the RPC."""
1199
medium = self._client._medium
1200
if medium._is_remote_before((1, 2)):
1201
# We already found out that the server can't understand
1202
# Repository.get_parent_map requests, so just fetch the whole
1205
# Note that this reads the whole graph, when only some keys are
1206
# wanted. On this old server there's no way (?) to get them all
1207
# in one go, and the user probably will have seen a warning about
1208
# the server being old anyhow.
1209
rg = self._get_revision_graph(None)
1210
# There is an api discrepency between get_parent_map and
1211
# get_revision_graph. Specifically, a "key:()" pair in
1212
# get_revision_graph just means a node has no parents. For
1213
# "get_parent_map" it means the node is a ghost. So fix up the
1214
# graph to correct this.
1215
# https://bugs.launchpad.net/bzr/+bug/214894
1216
# There is one other "bug" which is that ghosts in
1217
# get_revision_graph() are not returned at all. But we won't worry
1218
# about that for now.
1219
for node_id, parent_ids in rg.iteritems():
1220
if parent_ids == ():
1221
rg[node_id] = (NULL_REVISION,)
1222
rg[NULL_REVISION] = ()
1227
raise ValueError('get_parent_map(None) is not valid')
1228
if NULL_REVISION in keys:
1229
keys.discard(NULL_REVISION)
1230
found_parents = {NULL_REVISION:()}
1232
return found_parents
1235
# TODO(Needs analysis): We could assume that the keys being requested
1236
# from get_parent_map are in a breadth first search, so typically they
1237
# will all be depth N from some common parent, and we don't have to
1238
# have the server iterate from the root parent, but rather from the
1239
# keys we're searching; and just tell the server the keyspace we
1240
# already have; but this may be more traffic again.
1242
# Transform self._parents_map into a search request recipe.
1243
# TODO: Manage this incrementally to avoid covering the same path
1244
# repeatedly. (The server will have to on each request, but the less
1245
# work done the better).
1247
# Negative caching notes:
1248
# new server sends missing when a request including the revid
1249
# 'include-missing:' is present in the request.
1250
# missing keys are serialised as missing:X, and we then call
1251
# provider.note_missing(X) for-all X
1252
parents_map = self._unstacked_provider.get_cached_map()
1253
if parents_map is None:
1254
# Repository is not locked, so there's no cache.
1256
# start_set is all the keys in the cache
1257
start_set = set(parents_map)
1258
# result set is all the references to keys in the cache
1259
result_parents = set()
1260
for parents in parents_map.itervalues():
1261
result_parents.update(parents)
1262
stop_keys = result_parents.difference(start_set)
1263
# We don't need to send ghosts back to the server as a position to
1265
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1266
key_count = len(parents_map)
1267
if (NULL_REVISION in result_parents
1268
and NULL_REVISION in self._unstacked_provider.missing_keys):
1269
# If we pruned NULL_REVISION from the stop_keys because it's also
1270
# in our cache of "missing" keys we need to increment our key count
1271
# by 1, because the reconsitituted SearchResult on the server will
1272
# still consider NULL_REVISION to be an included key.
1274
included_keys = start_set.intersection(result_parents)
1275
start_set.difference_update(included_keys)
1276
recipe = ('manual', start_set, stop_keys, key_count)
1277
body = self._serialise_search_recipe(recipe)
1278
path = self.bzrdir._path_for_remote_call(self._client)
1280
if type(key) is not str:
1282
"key %r not a plain string" % (key,))
1283
verb = 'Repository.get_parent_map'
1284
args = (path, 'include-missing:') + tuple(keys)
1286
response = self._call_with_body_bytes_expecting_body(
1288
except errors.UnknownSmartMethod:
1289
# Server does not support this method, so get the whole graph.
1290
# Worse, we have to force a disconnection, because the server now
1291
# doesn't realise it has a body on the wire to consume, so the
1292
# only way to recover is to abandon the connection.
1294
'Server is too old for fast get_parent_map, reconnecting. '
1295
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1297
# To avoid having to disconnect repeatedly, we keep track of the
1298
# fact the server doesn't understand remote methods added in 1.2.
1299
medium._remember_remote_is_before((1, 2))
1300
# Recurse just once and we should use the fallback code.
1301
return self._get_parent_map_rpc(keys)
1302
response_tuple, response_handler = response
1303
if response_tuple[0] not in ['ok']:
1304
response_handler.cancel_read_body()
1305
raise errors.UnexpectedSmartServerResponse(response_tuple)
1306
if response_tuple[0] == 'ok':
1307
coded = bz2.decompress(response_handler.read_body_bytes())
1309
# no revisions found
1311
lines = coded.split('\n')
1314
d = tuple(line.split())
1316
revision_graph[d[0]] = d[1:]
1319
if d[0].startswith('missing:'):
1321
self._unstacked_provider.note_missing_key(revid)
1323
# no parents - so give the Graph result
1325
revision_graph[d[0]] = (NULL_REVISION,)
1326
return revision_graph
1329
def get_signature_text(self, revision_id):
1331
return self._real_repository.get_signature_text(revision_id)
1334
def get_inventory_xml(self, revision_id):
1336
return self._real_repository.get_inventory_xml(revision_id)
1338
def deserialise_inventory(self, revision_id, xml):
1340
return self._real_repository.deserialise_inventory(revision_id, xml)
1342
def reconcile(self, other=None, thorough=False):
1344
return self._real_repository.reconcile(other=other, thorough=thorough)
1346
def all_revision_ids(self):
1348
return self._real_repository.all_revision_ids()
1351
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1353
return self._real_repository.get_deltas_for_revisions(revisions,
1354
specific_fileids=specific_fileids)
1357
def get_revision_delta(self, revision_id, specific_fileids=None):
1359
return self._real_repository.get_revision_delta(revision_id,
1360
specific_fileids=specific_fileids)
1363
def revision_trees(self, revision_ids):
1365
return self._real_repository.revision_trees(revision_ids)
1368
def get_revision_reconcile(self, revision_id):
1370
return self._real_repository.get_revision_reconcile(revision_id)
1373
def check(self, revision_ids=None):
1375
return self._real_repository.check(revision_ids=revision_ids)
1377
def copy_content_into(self, destination, revision_id=None):
1379
return self._real_repository.copy_content_into(
1380
destination, revision_id=revision_id)
1382
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1383
# get a tarball of the remote repository, and copy from that into the
1385
from bzrlib import osutils
1387
# TODO: Maybe a progress bar while streaming the tarball?
1388
note("Copying repository content as tarball...")
1389
tar_file = self._get_tarball('bz2')
1390
if tar_file is None:
1392
destination = to_bzrdir.create_repository()
1394
tar = tarfile.open('repository', fileobj=tar_file,
1396
tmpdir = osutils.mkdtemp()
1398
_extract_tar(tar, tmpdir)
1399
tmp_bzrdir = BzrDir.open(tmpdir)
1400
tmp_repo = tmp_bzrdir.open_repository()
1401
tmp_repo.copy_content_into(destination, revision_id)
1403
osutils.rmtree(tmpdir)
1407
# TODO: Suggestion from john: using external tar is much faster than
1408
# python's tarfile library, but it may not work on windows.
1411
def inventories(self):
1412
"""Decorate the real repository for now.
1414
In the long term a full blown network facility is needed to
1415
avoid creating a real repository object locally.
1418
return self._real_repository.inventories
1422
"""Compress the data within the repository.
1424
This is not currently implemented within the smart server.
1427
return self._real_repository.pack()
1430
def revisions(self):
1431
"""Decorate the real repository for now.
1433
In the short term this should become a real object to intercept graph
1436
In the long term a full blown network facility is needed.
1439
return self._real_repository.revisions
1441
def set_make_working_trees(self, new_value):
1443
new_value_str = "True"
1445
new_value_str = "False"
1446
path = self.bzrdir._path_for_remote_call(self._client)
1448
response = self._call(
1449
'Repository.set_make_working_trees', path, new_value_str)
1450
except errors.UnknownSmartMethod:
1452
self._real_repository.set_make_working_trees(new_value)
1454
if response[0] != 'ok':
1455
raise errors.UnexpectedSmartServerResponse(response)
1458
def signatures(self):
1459
"""Decorate the real repository for now.
1461
In the long term a full blown network facility is needed to avoid
1462
creating a real repository object locally.
1465
return self._real_repository.signatures
1468
def sign_revision(self, revision_id, gpg_strategy):
1470
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1474
"""Decorate the real repository for now.
1476
In the long term a full blown network facility is needed to avoid
1477
creating a real repository object locally.
1480
return self._real_repository.texts
1483
def get_revisions(self, revision_ids):
1485
return self._real_repository.get_revisions(revision_ids)
1487
def supports_rich_root(self):
1488
return self._format.rich_root_data
1490
def iter_reverse_revision_history(self, revision_id):
1492
return self._real_repository.iter_reverse_revision_history(revision_id)
1495
def _serializer(self):
1496
return self._format._serializer
1498
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1500
return self._real_repository.store_revision_signature(
1501
gpg_strategy, plaintext, revision_id)
1503
def add_signature_text(self, revision_id, signature):
1505
return self._real_repository.add_signature_text(revision_id, signature)
1507
def has_signature_for_revision_id(self, revision_id):
1509
return self._real_repository.has_signature_for_revision_id(revision_id)
1511
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1513
return self._real_repository.item_keys_introduced_by(revision_ids,
1514
_files_pb=_files_pb)
1516
def revision_graph_can_have_wrong_parents(self):
1517
# The answer depends on the remote repo format.
1519
return self._real_repository.revision_graph_can_have_wrong_parents()
1521
def _find_inconsistent_revision_parents(self):
1523
return self._real_repository._find_inconsistent_revision_parents()
1525
def _check_for_inconsistent_revision_parents(self):
1527
return self._real_repository._check_for_inconsistent_revision_parents()
1529
def _make_parents_provider(self, other=None):
1530
providers = [self._unstacked_provider]
1531
if other is not None:
1532
providers.insert(0, other)
1533
providers.extend(r._make_parents_provider() for r in
1534
self._fallback_repositories)
1535
return graph._StackedParentsProvider(providers)
1537
def _serialise_search_recipe(self, recipe):
1538
"""Serialise a graph search recipe.
1540
:param recipe: A search recipe (start, stop, count).
1541
:return: Serialised bytes.
1543
start_keys = ' '.join(recipe[1])
1544
stop_keys = ' '.join(recipe[2])
1545
count = str(recipe[3])
1546
return '\n'.join((start_keys, stop_keys, count))
1548
def _serialise_search_result(self, search_result):
1549
if isinstance(search_result, graph.PendingAncestryResult):
1550
parts = ['ancestry-of']
1551
parts.extend(search_result.heads)
1553
recipe = search_result.get_recipe()
1554
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1555
return '\n'.join(parts)
1558
path = self.bzrdir._path_for_remote_call(self._client)
1560
response = self._call('PackRepository.autopack', path)
1561
except errors.UnknownSmartMethod:
1563
self._real_repository._pack_collection.autopack()
1566
if response[0] != 'ok':
1567
raise errors.UnexpectedSmartServerResponse(response)
1570
class RemoteStreamSink(repository.StreamSink):
1572
def _insert_real(self, stream, src_format, resume_tokens):
1573
self.target_repo._ensure_real()
1574
sink = self.target_repo._real_repository._get_sink()
1575
result = sink.insert_stream(stream, src_format, resume_tokens)
1577
self.target_repo.autopack()
1580
def insert_stream(self, stream, src_format, resume_tokens):
1581
target = self.target_repo
1582
if target._lock_token:
1583
verb = 'Repository.insert_stream_locked'
1584
extra_args = (target._lock_token or '',)
1585
required_version = (1, 14)
1587
verb = 'Repository.insert_stream'
1589
required_version = (1, 13)
1590
client = target._client
1591
medium = client._medium
1592
if medium._is_remote_before(required_version):
1593
# No possible way this can work.
1594
return self._insert_real(stream, src_format, resume_tokens)
1595
path = target.bzrdir._path_for_remote_call(client)
1596
if not resume_tokens:
1597
# XXX: Ugly but important for correctness, *will* be fixed during
1598
# 1.13 cycle. Pushing a stream that is interrupted results in a
1599
# fallback to the _real_repositories sink *with a partial stream*.
1600
# Thats bad because we insert less data than bzr expected. To avoid
1601
# this we do a trial push to make sure the verb is accessible, and
1602
# do not fallback when actually pushing the stream. A cleanup patch
1603
# is going to look at rewinding/restarting the stream/partial
1605
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1607
response = client.call_with_body_stream(
1608
(verb, path, '') + extra_args, byte_stream)
1609
except errors.UnknownSmartMethod:
1610
medium._remember_remote_is_before(required_version)
1611
return self._insert_real(stream, src_format, resume_tokens)
1612
byte_stream = smart_repo._stream_to_byte_stream(
1614
resume_tokens = ' '.join(resume_tokens)
1615
response = client.call_with_body_stream(
1616
(verb, path, resume_tokens) + extra_args, byte_stream)
1617
if response[0][0] not in ('ok', 'missing-basis'):
1618
raise errors.UnexpectedSmartServerResponse(response)
1619
if response[0][0] == 'missing-basis':
1620
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1621
resume_tokens = tokens
1622
return resume_tokens, missing_keys
1624
self.target_repo.refresh_data()
1628
class RemoteStreamSource(repository.StreamSource):
1629
"""Stream data from a remote server."""
1631
def get_stream(self, search):
1632
if (self.from_repository._fallback_repositories and
1633
self.to_format._fetch_order == 'topological'):
1634
return self._real_stream(self.from_repository, search)
1635
return self.missing_parents_chain(search, [self.from_repository] +
1636
self.from_repository._fallback_repositories)
1638
def _real_stream(self, repo, search):
1639
"""Get a stream for search from repo.
1641
This never called RemoteStreamSource.get_stream, and is a heler
1642
for RemoteStreamSource._get_stream to allow getting a stream
1643
reliably whether fallback back because of old servers or trying
1644
to stream from a non-RemoteRepository (which the stacked support
1647
source = repo._get_source(self.to_format)
1648
if isinstance(source, RemoteStreamSource):
1649
return repository.StreamSource.get_stream(source, search)
1650
return source.get_stream(search)
1652
def _get_stream(self, repo, search):
1653
"""Core worker to get a stream from repo for search.
1655
This is used by both get_stream and the stacking support logic. It
1656
deliberately gets a stream for repo which does not need to be
1657
self.from_repository. In the event that repo is not Remote, or
1658
cannot do a smart stream, a fallback is made to the generic
1659
repository._get_stream() interface, via self._real_stream.
1661
In the event of stacking, streams from _get_stream will not
1662
contain all the data for search - this is normal (see get_stream).
1664
:param repo: A repository.
1665
:param search: A search.
1667
# Fallbacks may be non-smart
1668
if not isinstance(repo, RemoteRepository):
1669
return self._real_stream(repo, search)
1670
client = repo._client
1671
medium = client._medium
1672
if medium._is_remote_before((1, 13)):
1673
# streaming was added in 1.13
1674
return self._real_stream(repo, search)
1675
path = repo.bzrdir._path_for_remote_call(client)
1677
search_bytes = repo._serialise_search_result(search)
1678
response = repo._call_with_body_bytes_expecting_body(
1679
'Repository.get_stream',
1680
(path, self.to_format.network_name()), search_bytes)
1681
response_tuple, response_handler = response
1682
except errors.UnknownSmartMethod:
1683
medium._remember_remote_is_before((1,13))
1684
return self._real_stream(repo, search)
1685
if response_tuple[0] != 'ok':
1686
raise errors.UnexpectedSmartServerResponse(response_tuple)
1687
byte_stream = response_handler.read_streamed_body()
1688
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1689
if src_format.network_name() != repo._format.network_name():
1690
raise AssertionError(
1691
"Mismatched RemoteRepository and stream src %r, %r" % (
1692
src_format.network_name(), repo._format.network_name()))
1695
def missing_parents_chain(self, search, sources):
1696
"""Chain multiple streams together to handle stacking.
1698
:param search: The overall search to satisfy with streams.
1699
:param sources: A list of Repository objects to query.
1701
self.serialiser = self.to_format._serializer
1702
self.seen_revs = set()
1703
self.referenced_revs = set()
1704
# If there are heads in the search, or the key count is > 0, we are not
1706
while not search.is_empty() and len(sources) > 1:
1707
source = sources.pop(0)
1708
stream = self._get_stream(source, search)
1709
for kind, substream in stream:
1710
if kind != 'revisions':
1711
yield kind, substream
1713
yield kind, self.missing_parents_rev_handler(substream)
1714
search = search.refine(self.seen_revs, self.referenced_revs)
1715
self.seen_revs = set()
1716
self.referenced_revs = set()
1717
if not search.is_empty():
1718
for kind, stream in self._get_stream(sources[0], search):
1721
def missing_parents_rev_handler(self, substream):
1722
for content in substream:
1723
revision_bytes = content.get_bytes_as('fulltext')
1724
revision = self.serialiser.read_revision_from_string(revision_bytes)
1725
self.seen_revs.add(content.key[-1])
1726
self.referenced_revs.update(revision.parent_ids)
1730
class RemoteBranchLockableFiles(LockableFiles):
1731
"""A 'LockableFiles' implementation that talks to a smart server.
1733
This is not a public interface class.
1736
def __init__(self, bzrdir, _client):
1737
self.bzrdir = bzrdir
1738
self._client = _client
1739
self._need_find_modes = True
1740
LockableFiles.__init__(
1741
self, bzrdir.get_branch_transport(None),
1742
'lock', lockdir.LockDir)
1744
def _find_modes(self):
1745
# RemoteBranches don't let the client set the mode of control files.
1746
self._dir_mode = None
1747
self._file_mode = None
1750
class RemoteBranchFormat(branch.BranchFormat):
1752
def __init__(self, network_name=None):
1753
super(RemoteBranchFormat, self).__init__()
1754
self._matchingbzrdir = RemoteBzrDirFormat()
1755
self._matchingbzrdir.set_branch_format(self)
1756
self._custom_format = None
1757
self._network_name = network_name
1759
def __eq__(self, other):
1760
return (isinstance(other, RemoteBranchFormat) and
1761
self.__dict__ == other.__dict__)
1763
def _ensure_real(self):
1764
if self._custom_format is None:
1765
self._custom_format = branch.network_format_registry.get(
1768
def get_format_description(self):
1769
return 'Remote BZR Branch'
1771
def network_name(self):
1772
return self._network_name
1774
def open(self, a_bzrdir, ignore_fallbacks=False):
1775
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1777
def _vfs_initialize(self, a_bzrdir):
1778
# Initialisation when using a local bzrdir object, or a non-vfs init
1779
# method is not available on the server.
1780
# self._custom_format is always set - the start of initialize ensures
1782
if isinstance(a_bzrdir, RemoteBzrDir):
1783
a_bzrdir._ensure_real()
1784
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1786
# We assume the bzrdir is parameterised; it may not be.
1787
result = self._custom_format.initialize(a_bzrdir)
1788
if (isinstance(a_bzrdir, RemoteBzrDir) and
1789
not isinstance(result, RemoteBranch)):
1790
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1793
def initialize(self, a_bzrdir):
1794
# 1) get the network name to use.
1795
if self._custom_format:
1796
network_name = self._custom_format.network_name()
1798
# Select the current bzrlib default and ask for that.
1799
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1800
reference_format = reference_bzrdir_format.get_branch_format()
1801
self._custom_format = reference_format
1802
network_name = reference_format.network_name()
1803
# Being asked to create on a non RemoteBzrDir:
1804
if not isinstance(a_bzrdir, RemoteBzrDir):
1805
return self._vfs_initialize(a_bzrdir)
1806
medium = a_bzrdir._client._medium
1807
if medium._is_remote_before((1, 13)):
1808
return self._vfs_initialize(a_bzrdir)
1809
# Creating on a remote bzr dir.
1810
# 2) try direct creation via RPC
1811
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1812
verb = 'BzrDir.create_branch'
1814
response = a_bzrdir._call(verb, path, network_name)
1815
except errors.UnknownSmartMethod:
1816
# Fallback - use vfs methods
1817
medium._remember_remote_is_before((1, 13))
1818
return self._vfs_initialize(a_bzrdir)
1819
if response[0] != 'ok':
1820
raise errors.UnexpectedSmartServerResponse(response)
1821
# Turn the response into a RemoteRepository object.
1822
format = RemoteBranchFormat(network_name=response[1])
1823
repo_format = response_tuple_to_repo_format(response[3:])
1824
if response[2] == '':
1825
repo_bzrdir = a_bzrdir
1827
repo_bzrdir = RemoteBzrDir(
1828
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1830
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1831
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1832
format=format, setup_stacking=False)
1833
# XXX: We know this is a new branch, so it must have revno 0, revid
1834
# NULL_REVISION. Creating the branch locked would make this be unable
1835
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1836
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1837
return remote_branch
1839
def make_tags(self, branch):
1841
return self._custom_format.make_tags(branch)
1843
def supports_tags(self):
1844
# Remote branches might support tags, but we won't know until we
1845
# access the real remote branch.
1847
return self._custom_format.supports_tags()
1849
def supports_stacking(self):
1851
return self._custom_format.supports_stacking()
1854
class RemoteBranch(branch.Branch, _RpcHelper):
1855
"""Branch stored on a server accessed by HPSS RPC.
1857
At the moment most operations are mapped down to simple file operations.
1860
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1861
_client=None, format=None, setup_stacking=True):
1862
"""Create a RemoteBranch instance.
1864
:param real_branch: An optional local implementation of the branch
1865
format, usually accessing the data via the VFS.
1866
:param _client: Private parameter for testing.
1867
:param format: A RemoteBranchFormat object, None to create one
1868
automatically. If supplied it should have a network_name already
1870
:param setup_stacking: If True make an RPC call to determine the
1871
stacked (or not) status of the branch. If False assume the branch
1874
# We intentionally don't call the parent class's __init__, because it
1875
# will try to assign to self.tags, which is a property in this subclass.
1876
# And the parent's __init__ doesn't do much anyway.
1877
self._revision_id_to_revno_cache = None
1878
self._partial_revision_id_to_revno_cache = {}
1879
self._revision_history_cache = None
1880
self._last_revision_info_cache = None
1881
self._merge_sorted_revisions_cache = None
1882
self.bzrdir = remote_bzrdir
1883
if _client is not None:
1884
self._client = _client
1886
self._client = remote_bzrdir._client
1887
self.repository = remote_repository
1888
if real_branch is not None:
1889
self._real_branch = real_branch
1890
# Give the remote repository the matching real repo.
1891
real_repo = self._real_branch.repository
1892
if isinstance(real_repo, RemoteRepository):
1893
real_repo._ensure_real()
1894
real_repo = real_repo._real_repository
1895
self.repository._set_real_repository(real_repo)
1896
# Give the branch the remote repository to let fast-pathing happen.
1897
self._real_branch.repository = self.repository
1899
self._real_branch = None
1900
# Fill out expected attributes of branch for bzrlib api users.
1901
self.base = self.bzrdir.root_transport.base
1902
self._control_files = None
1903
self._lock_mode = None
1904
self._lock_token = None
1905
self._repo_lock_token = None
1906
self._lock_count = 0
1907
self._leave_lock = False
1908
# Setup a format: note that we cannot call _ensure_real until all the
1909
# attributes above are set: This code cannot be moved higher up in this
1912
self._format = RemoteBranchFormat()
1913
if real_branch is not None:
1914
self._format._network_name = \
1915
self._real_branch._format.network_name()
1917
self._format = format
1918
if not self._format._network_name:
1919
# Did not get from open_branchV2 - old server.
1921
self._format._network_name = \
1922
self._real_branch._format.network_name()
1923
self.tags = self._format.make_tags(self)
1924
# The base class init is not called, so we duplicate this:
1925
hooks = branch.Branch.hooks['open']
1929
self._setup_stacking()
1931
def _setup_stacking(self):
1932
# configure stacking into the remote repository, by reading it from
1935
fallback_url = self.get_stacked_on_url()
1936
except (errors.NotStacked, errors.UnstackableBranchFormat,
1937
errors.UnstackableRepositoryFormat), e:
1939
self._activate_fallback_location(fallback_url)
1941
def _get_config(self):
1942
return RemoteBranchConfig(self)
1944
def _get_real_transport(self):
1945
# if we try vfs access, return the real branch's vfs transport
1947
return self._real_branch._transport
1949
_transport = property(_get_real_transport)
1952
return "%s(%s)" % (self.__class__.__name__, self.base)
1956
def _ensure_real(self):
1957
"""Ensure that there is a _real_branch set.
1959
Used before calls to self._real_branch.
1961
if self._real_branch is None:
1962
if not vfs.vfs_enabled():
1963
raise AssertionError('smart server vfs must be enabled '
1964
'to use vfs implementation')
1965
self.bzrdir._ensure_real()
1966
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1967
if self.repository._real_repository is None:
1968
# Give the remote repository the matching real repo.
1969
real_repo = self._real_branch.repository
1970
if isinstance(real_repo, RemoteRepository):
1971
real_repo._ensure_real()
1972
real_repo = real_repo._real_repository
1973
self.repository._set_real_repository(real_repo)
1974
# Give the real branch the remote repository to let fast-pathing
1976
self._real_branch.repository = self.repository
1977
if self._lock_mode == 'r':
1978
self._real_branch.lock_read()
1979
elif self._lock_mode == 'w':
1980
self._real_branch.lock_write(token=self._lock_token)
1982
def _translate_error(self, err, **context):
1983
self.repository._translate_error(err, branch=self, **context)
1985
def _clear_cached_state(self):
1986
super(RemoteBranch, self)._clear_cached_state()
1987
if self._real_branch is not None:
1988
self._real_branch._clear_cached_state()
1990
def _clear_cached_state_of_remote_branch_only(self):
1991
"""Like _clear_cached_state, but doesn't clear the cache of
1994
This is useful when falling back to calling a method of
1995
self._real_branch that changes state. In that case the underlying
1996
branch changes, so we need to invalidate this RemoteBranch's cache of
1997
it. However, there's no need to invalidate the _real_branch's cache
1998
too, in fact doing so might harm performance.
2000
super(RemoteBranch, self)._clear_cached_state()
2003
def control_files(self):
2004
# Defer actually creating RemoteBranchLockableFiles until its needed,
2005
# because it triggers an _ensure_real that we otherwise might not need.
2006
if self._control_files is None:
2007
self._control_files = RemoteBranchLockableFiles(
2008
self.bzrdir, self._client)
2009
return self._control_files
2011
def _get_checkout_format(self):
2013
return self._real_branch._get_checkout_format()
2015
def get_physical_lock_status(self):
2016
"""See Branch.get_physical_lock_status()."""
2017
# should be an API call to the server, as branches must be lockable.
2019
return self._real_branch.get_physical_lock_status()
2021
def get_stacked_on_url(self):
2022
"""Get the URL this branch is stacked against.
2024
:raises NotStacked: If the branch is not stacked.
2025
:raises UnstackableBranchFormat: If the branch does not support
2027
:raises UnstackableRepositoryFormat: If the repository does not support
2031
# there may not be a repository yet, so we can't use
2032
# self._translate_error, so we can't use self._call either.
2033
response = self._client.call('Branch.get_stacked_on_url',
2034
self._remote_path())
2035
except errors.ErrorFromSmartServer, err:
2036
# there may not be a repository yet, so we can't call through
2037
# its _translate_error
2038
_translate_error(err, branch=self)
2039
except errors.UnknownSmartMethod, err:
2041
return self._real_branch.get_stacked_on_url()
2042
if response[0] != 'ok':
2043
raise errors.UnexpectedSmartServerResponse(response)
2046
def _vfs_get_tags_bytes(self):
2048
return self._real_branch._get_tags_bytes()
2050
def _get_tags_bytes(self):
2051
medium = self._client._medium
2052
if medium._is_remote_before((1, 13)):
2053
return self._vfs_get_tags_bytes()
2055
response = self._call('Branch.get_tags_bytes', self._remote_path())
2056
except errors.UnknownSmartMethod:
2057
medium._remember_remote_is_before((1, 13))
2058
return self._vfs_get_tags_bytes()
2061
def lock_read(self):
2062
self.repository.lock_read()
2063
if not self._lock_mode:
2064
self._lock_mode = 'r'
2065
self._lock_count = 1
2066
if self._real_branch is not None:
2067
self._real_branch.lock_read()
2069
self._lock_count += 1
2071
def _remote_lock_write(self, token):
2073
branch_token = repo_token = ''
2075
branch_token = token
2076
repo_token = self.repository.lock_write()
2077
self.repository.unlock()
2078
err_context = {'token': token}
2079
response = self._call(
2080
'Branch.lock_write', self._remote_path(), branch_token,
2081
repo_token or '', **err_context)
2082
if response[0] != 'ok':
2083
raise errors.UnexpectedSmartServerResponse(response)
2084
ok, branch_token, repo_token = response
2085
return branch_token, repo_token
2087
def lock_write(self, token=None):
2088
if not self._lock_mode:
2089
# Lock the branch and repo in one remote call.
2090
remote_tokens = self._remote_lock_write(token)
2091
self._lock_token, self._repo_lock_token = remote_tokens
2092
if not self._lock_token:
2093
raise SmartProtocolError('Remote server did not return a token!')
2094
# Tell the self.repository object that it is locked.
2095
self.repository.lock_write(
2096
self._repo_lock_token, _skip_rpc=True)
2098
if self._real_branch is not None:
2099
self._real_branch.lock_write(token=self._lock_token)
2100
if token is not None:
2101
self._leave_lock = True
2103
self._leave_lock = False
2104
self._lock_mode = 'w'
2105
self._lock_count = 1
2106
elif self._lock_mode == 'r':
2107
raise errors.ReadOnlyTransaction
2109
if token is not None:
2110
# A token was given to lock_write, and we're relocking, so
2111
# check that the given token actually matches the one we
2113
if token != self._lock_token:
2114
raise errors.TokenMismatch(token, self._lock_token)
2115
self._lock_count += 1
2116
# Re-lock the repository too.
2117
self.repository.lock_write(self._repo_lock_token)
2118
return self._lock_token or None
2120
def _set_tags_bytes(self, bytes):
2122
return self._real_branch._set_tags_bytes(bytes)
2124
def _unlock(self, branch_token, repo_token):
2125
err_context = {'token': str((branch_token, repo_token))}
2126
response = self._call(
2127
'Branch.unlock', self._remote_path(), branch_token,
2128
repo_token or '', **err_context)
2129
if response == ('ok',):
2131
raise errors.UnexpectedSmartServerResponse(response)
2135
self._lock_count -= 1
2136
if not self._lock_count:
2137
self._clear_cached_state()
2138
mode = self._lock_mode
2139
self._lock_mode = None
2140
if self._real_branch is not None:
2141
if (not self._leave_lock and mode == 'w' and
2142
self._repo_lock_token):
2143
# If this RemoteBranch will remove the physical lock
2144
# for the repository, make sure the _real_branch
2145
# doesn't do it first. (Because the _real_branch's
2146
# repository is set to be the RemoteRepository.)
2147
self._real_branch.repository.leave_lock_in_place()
2148
self._real_branch.unlock()
2150
# Only write-locked branched need to make a remote method
2151
# call to perfom the unlock.
2153
if not self._lock_token:
2154
raise AssertionError('Locked, but no token!')
2155
branch_token = self._lock_token
2156
repo_token = self._repo_lock_token
2157
self._lock_token = None
2158
self._repo_lock_token = None
2159
if not self._leave_lock:
2160
self._unlock(branch_token, repo_token)
2162
self.repository.unlock()
2164
def break_lock(self):
2166
return self._real_branch.break_lock()
2168
def leave_lock_in_place(self):
2169
if not self._lock_token:
2170
raise NotImplementedError(self.leave_lock_in_place)
2171
self._leave_lock = True
2173
def dont_leave_lock_in_place(self):
2174
if not self._lock_token:
2175
raise NotImplementedError(self.dont_leave_lock_in_place)
2176
self._leave_lock = False
2178
def _last_revision_info(self):
2179
response = self._call('Branch.last_revision_info', self._remote_path())
2180
if response[0] != 'ok':
2181
raise SmartProtocolError('unexpected response code %s' % (response,))
2182
revno = int(response[1])
2183
last_revision = response[2]
2184
return (revno, last_revision)
2186
def _gen_revision_history(self):
2187
"""See Branch._gen_revision_history()."""
2188
response_tuple, response_handler = self._call_expecting_body(
2189
'Branch.revision_history', self._remote_path())
2190
if response_tuple[0] != 'ok':
2191
raise errors.UnexpectedSmartServerResponse(response_tuple)
2192
result = response_handler.read_body_bytes().split('\x00')
2197
def _remote_path(self):
2198
return self.bzrdir._path_for_remote_call(self._client)
2200
def _set_last_revision_descendant(self, revision_id, other_branch,
2201
allow_diverged=False, allow_overwrite_descendant=False):
2202
# This performs additional work to meet the hook contract; while its
2203
# undesirable, we have to synthesise the revno to call the hook, and
2204
# not calling the hook is worse as it means changes can't be prevented.
2205
# Having calculated this though, we can't just call into
2206
# set_last_revision_info as a simple call, because there is a set_rh
2207
# hook that some folk may still be using.
2208
old_revno, old_revid = self.last_revision_info()
2209
history = self._lefthand_history(revision_id)
2210
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2211
err_context = {'other_branch': other_branch}
2212
response = self._call('Branch.set_last_revision_ex',
2213
self._remote_path(), self._lock_token, self._repo_lock_token,
2214
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2216
self._clear_cached_state()
2217
if len(response) != 3 and response[0] != 'ok':
2218
raise errors.UnexpectedSmartServerResponse(response)
2219
new_revno, new_revision_id = response[1:]
2220
self._last_revision_info_cache = new_revno, new_revision_id
2221
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2222
if self._real_branch is not None:
2223
cache = new_revno, new_revision_id
2224
self._real_branch._last_revision_info_cache = cache
2226
def _set_last_revision(self, revision_id):
2227
old_revno, old_revid = self.last_revision_info()
2228
# This performs additional work to meet the hook contract; while its
2229
# undesirable, we have to synthesise the revno to call the hook, and
2230
# not calling the hook is worse as it means changes can't be prevented.
2231
# Having calculated this though, we can't just call into
2232
# set_last_revision_info as a simple call, because there is a set_rh
2233
# hook that some folk may still be using.
2234
history = self._lefthand_history(revision_id)
2235
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2236
self._clear_cached_state()
2237
response = self._call('Branch.set_last_revision',
2238
self._remote_path(), self._lock_token, self._repo_lock_token,
2240
if response != ('ok',):
2241
raise errors.UnexpectedSmartServerResponse(response)
2242
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2245
def set_revision_history(self, rev_history):
2246
# Send just the tip revision of the history; the server will generate
2247
# the full history from that. If the revision doesn't exist in this
2248
# branch, NoSuchRevision will be raised.
2249
if rev_history == []:
2252
rev_id = rev_history[-1]
2253
self._set_last_revision(rev_id)
2254
for hook in branch.Branch.hooks['set_rh']:
2255
hook(self, rev_history)
2256
self._cache_revision_history(rev_history)
2258
def _get_parent_location(self):
2259
medium = self._client._medium
2260
if medium._is_remote_before((1, 13)):
2261
return self._vfs_get_parent_location()
2263
response = self._call('Branch.get_parent', self._remote_path())
2264
except errors.UnknownSmartMethod:
2265
medium._remember_remote_is_before((1, 13))
2266
return self._vfs_get_parent_location()
2267
if len(response) != 1:
2268
raise errors.UnexpectedSmartServerResponse(response)
2269
parent_location = response[0]
2270
if parent_location == '':
2272
return parent_location
2274
def _vfs_get_parent_location(self):
2276
return self._real_branch._get_parent_location()
2278
def set_parent(self, url):
2280
return self._real_branch.set_parent(url)
2282
def _set_parent_location(self, url):
2283
# Used by tests, to poke bad urls into branch configurations
2285
self.set_parent(url)
2288
return self._real_branch._set_parent_location(url)
2291
def pull(self, source, overwrite=False, stop_revision=None,
2293
self._clear_cached_state_of_remote_branch_only()
2295
return self._real_branch.pull(
2296
source, overwrite=overwrite, stop_revision=stop_revision,
2297
_override_hook_target=self, **kwargs)
2300
def push(self, target, overwrite=False, stop_revision=None):
2302
return self._real_branch.push(
2303
target, overwrite=overwrite, stop_revision=stop_revision,
2304
_override_hook_source_branch=self)
2306
def is_locked(self):
2307
return self._lock_count >= 1
2310
def revision_id_to_revno(self, revision_id):
2312
return self._real_branch.revision_id_to_revno(revision_id)
2315
def set_last_revision_info(self, revno, revision_id):
2316
# XXX: These should be returned by the set_last_revision_info verb
2317
old_revno, old_revid = self.last_revision_info()
2318
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2319
revision_id = ensure_null(revision_id)
2321
response = self._call('Branch.set_last_revision_info',
2322
self._remote_path(), self._lock_token, self._repo_lock_token,
2323
str(revno), revision_id)
2324
except errors.UnknownSmartMethod:
2326
self._clear_cached_state_of_remote_branch_only()
2327
self._real_branch.set_last_revision_info(revno, revision_id)
2328
self._last_revision_info_cache = revno, revision_id
2330
if response == ('ok',):
2331
self._clear_cached_state()
2332
self._last_revision_info_cache = revno, revision_id
2333
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2334
# Update the _real_branch's cache too.
2335
if self._real_branch is not None:
2336
cache = self._last_revision_info_cache
2337
self._real_branch._last_revision_info_cache = cache
2339
raise errors.UnexpectedSmartServerResponse(response)
2342
def generate_revision_history(self, revision_id, last_rev=None,
2344
medium = self._client._medium
2345
if not medium._is_remote_before((1, 6)):
2346
# Use a smart method for 1.6 and above servers
2348
self._set_last_revision_descendant(revision_id, other_branch,
2349
allow_diverged=True, allow_overwrite_descendant=True)
2351
except errors.UnknownSmartMethod:
2352
medium._remember_remote_is_before((1, 6))
2353
self._clear_cached_state_of_remote_branch_only()
2354
self.set_revision_history(self._lefthand_history(revision_id,
2355
last_rev=last_rev,other_branch=other_branch))
2357
def set_push_location(self, location):
2359
return self._real_branch.set_push_location(location)
2362
class RemoteBranchConfig(object):
2363
"""A Config that reads from a smart branch and writes via smart methods.
2365
It is a low-level object that considers config data to be name/value pairs
2366
that may be associated with a section. Assigning meaning to the these
2367
values is done at higher levels like bzrlib.config.TreeConfig.
2370
def __init__(self, branch):
2371
self._branch = branch
2373
def get_option(self, name, section=None, default=None):
2374
"""Return the value associated with a named option.
2376
:param name: The name of the value
2377
:param section: The section the option is in (if any)
2378
:param default: The value to return if the value is not set
2379
:return: The value or default value
2381
configobj = self._get_configobj()
2383
section_obj = configobj
2386
section_obj = configobj[section]
2389
return section_obj.get(name, default)
2391
def _get_configobj(self):
2392
path = self._branch.bzrdir._path_for_remote_call(
2393
self._branch._client)
2394
response = self._branch._client.call_expecting_body(
2395
'Branch.get_config_file', path)
2396
if response[0][0] != 'ok':
2397
raise UnexpectedSmartServerResponse(response)
2398
bytes = response[1].read_body_bytes()
2399
return config.ConfigObj([bytes], encoding='utf-8')
2401
def set_option(self, value, name, section=None):
2402
"""Set the value associated with a named option.
2404
:param value: The value to set
2405
:param name: The name of the value to set
2406
:param section: The section the option is in (if any)
2408
return self._vfs_set_option(value, name, section)
2410
def _vfs_set_option(self, value, name, section=None):
2411
self._branch._ensure_real()
2412
return self._branch._real_branch._get_config().set_option(
2413
value, name, section)
2416
def _extract_tar(tar, to_dir):
2417
"""Extract all the contents of a tarfile object.
2419
A replacement for extractall, which is not present in python2.4
2422
tar.extract(tarinfo, to_dir)
2425
def _translate_error(err, **context):
2426
"""Translate an ErrorFromSmartServer into a more useful error.
2428
Possible context keys:
2436
If the error from the server doesn't match a known pattern, then
2437
UnknownErrorFromSmartServer is raised.
2441
return context[name]
2442
except KeyError, key_err:
2443
mutter('Missing key %r in context %r', key_err.args[0], context)
2446
"""Get the path from the context if present, otherwise use first error
2450
return context['path']
2451
except KeyError, key_err:
2453
return err.error_args[0]
2454
except IndexError, idx_err:
2456
'Missing key %r in context %r', key_err.args[0], context)
2459
if err.error_verb == 'NoSuchRevision':
2460
raise NoSuchRevision(find('branch'), err.error_args[0])
2461
elif err.error_verb == 'nosuchrevision':
2462
raise NoSuchRevision(find('repository'), err.error_args[0])
2463
elif err.error_tuple == ('nobranch',):
2464
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2465
elif err.error_verb == 'norepository':
2466
raise errors.NoRepositoryPresent(find('bzrdir'))
2467
elif err.error_verb == 'LockContention':
2468
raise errors.LockContention('(remote lock)')
2469
elif err.error_verb == 'UnlockableTransport':
2470
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2471
elif err.error_verb == 'LockFailed':
2472
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2473
elif err.error_verb == 'TokenMismatch':
2474
raise errors.TokenMismatch(find('token'), '(remote token)')
2475
elif err.error_verb == 'Diverged':
2476
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2477
elif err.error_verb == 'TipChangeRejected':
2478
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2479
elif err.error_verb == 'UnstackableBranchFormat':
2480
raise errors.UnstackableBranchFormat(*err.error_args)
2481
elif err.error_verb == 'UnstackableRepositoryFormat':
2482
raise errors.UnstackableRepositoryFormat(*err.error_args)
2483
elif err.error_verb == 'NotStacked':
2484
raise errors.NotStacked(branch=find('branch'))
2485
elif err.error_verb == 'PermissionDenied':
2487
if len(err.error_args) >= 2:
2488
extra = err.error_args[1]
2491
raise errors.PermissionDenied(path, extra=extra)
2492
elif err.error_verb == 'ReadError':
2494
raise errors.ReadError(path)
2495
elif err.error_verb == 'NoSuchFile':
2497
raise errors.NoSuchFile(path)
2498
elif err.error_verb == 'FileExists':
2499
raise errors.FileExists(err.error_args[0])
2500
elif err.error_verb == 'DirectoryNotEmpty':
2501
raise errors.DirectoryNotEmpty(err.error_args[0])
2502
elif err.error_verb == 'ShortReadvError':
2503
args = err.error_args
2504
raise errors.ShortReadvError(
2505
args[0], int(args[1]), int(args[2]), int(args[3]))
2506
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2507
encoding = str(err.error_args[0]) # encoding must always be a string
2508
val = err.error_args[1]
2509
start = int(err.error_args[2])
2510
end = int(err.error_args[3])
2511
reason = str(err.error_args[4]) # reason must always be a string
2512
if val.startswith('u:'):
2513
val = val[2:].decode('utf-8')
2514
elif val.startswith('s:'):
2515
val = val[2:].decode('base64')
2516
if err.error_verb == 'UnicodeDecodeError':
2517
raise UnicodeDecodeError(encoding, val, start, end, reason)
2518
elif err.error_verb == 'UnicodeEncodeError':
2519
raise UnicodeEncodeError(encoding, val, start, end, reason)
2520
elif err.error_verb == 'ReadOnlyError':
2521
raise errors.TransportNotPossible('readonly transport')
2522
raise errors.UnknownErrorFromSmartServer(err)