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 perform 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)
642
"""Decorate the real repository for now.
644
In the long term a full blown network facility is needed to avoid
645
creating a real repository object locally.
648
return self._real_repository.chk_bytes
650
def commit_write_group(self):
651
"""Complete a write group on the decorated repository.
653
Smart methods perform operations in a single step so this API
654
is not really applicable except as a compatibility thunk
655
for older plugins that don't use e.g. the CommitBuilder
659
return self._real_repository.commit_write_group()
661
def resume_write_group(self, tokens):
663
return self._real_repository.resume_write_group(tokens)
665
def suspend_write_group(self):
667
return self._real_repository.suspend_write_group()
669
def get_missing_parent_inventories(self):
671
return self._real_repository.get_missing_parent_inventories()
673
def _ensure_real(self):
674
"""Ensure that there is a _real_repository set.
676
Used before calls to self._real_repository.
678
Note that _ensure_real causes many roundtrips to the server which are
679
not desirable, and prevents the use of smart one-roundtrip RPC's to
680
perform complex operations (such as accessing parent data, streaming
681
revisions etc). Adding calls to _ensure_real should only be done when
682
bringing up new functionality, adding fallbacks for smart methods that
683
require a fallback path, and never to replace an existing smart method
684
invocation. If in doubt chat to the bzr network team.
686
if self._real_repository is None:
687
self.bzrdir._ensure_real()
688
self._set_real_repository(
689
self.bzrdir._real_bzrdir.open_repository())
691
def _translate_error(self, err, **context):
692
self.bzrdir._translate_error(err, repository=self, **context)
694
def find_text_key_references(self):
695
"""Find the text key references within the repository.
697
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
698
revision_ids. Each altered file-ids has the exact revision_ids that
699
altered it listed explicitly.
700
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
701
to whether they were referred to by the inventory of the
702
revision_id that they contain. The inventory texts from all present
703
revision ids are assessed to generate this report.
706
return self._real_repository.find_text_key_references()
708
def _generate_text_key_index(self):
709
"""Generate a new text key index for the repository.
711
This is an expensive function that will take considerable time to run.
713
:return: A dict mapping (file_id, revision_id) tuples to a list of
714
parents, also (file_id, revision_id) tuples.
717
return self._real_repository._generate_text_key_index()
719
def _get_revision_graph(self, revision_id):
720
"""Private method for using with old (< 1.2) servers to fallback."""
721
if revision_id is None:
723
elif revision.is_null(revision_id):
726
path = self.bzrdir._path_for_remote_call(self._client)
727
response = self._call_expecting_body(
728
'Repository.get_revision_graph', path, revision_id)
729
response_tuple, response_handler = response
730
if response_tuple[0] != 'ok':
731
raise errors.UnexpectedSmartServerResponse(response_tuple)
732
coded = response_handler.read_body_bytes()
734
# no revisions in this repository!
736
lines = coded.split('\n')
739
d = tuple(line.split())
740
revision_graph[d[0]] = d[1:]
742
return revision_graph
745
"""See Repository._get_sink()."""
746
return RemoteStreamSink(self)
748
def _get_source(self, to_format):
749
"""Return a source for streaming from this repository."""
750
return RemoteStreamSource(self, to_format)
752
def has_revision(self, revision_id):
753
"""See Repository.has_revision()."""
754
if revision_id == NULL_REVISION:
755
# The null revision is always present.
757
path = self.bzrdir._path_for_remote_call(self._client)
758
response = self._call('Repository.has_revision', path, revision_id)
759
if response[0] not in ('yes', 'no'):
760
raise errors.UnexpectedSmartServerResponse(response)
761
if response[0] == 'yes':
763
for fallback_repo in self._fallback_repositories:
764
if fallback_repo.has_revision(revision_id):
768
def has_revisions(self, revision_ids):
769
"""See Repository.has_revisions()."""
770
# FIXME: This does many roundtrips, particularly when there are
771
# fallback repositories. -- mbp 20080905
773
for revision_id in revision_ids:
774
if self.has_revision(revision_id):
775
result.add(revision_id)
778
def has_same_location(self, other):
779
return (self.__class__ is other.__class__ and
780
self.bzrdir.transport.base == other.bzrdir.transport.base)
782
def get_graph(self, other_repository=None):
783
"""Return the graph for this repository format"""
784
parents_provider = self._make_parents_provider(other_repository)
785
return graph.Graph(parents_provider)
787
def gather_stats(self, revid=None, committers=None):
788
"""See Repository.gather_stats()."""
789
path = self.bzrdir._path_for_remote_call(self._client)
790
# revid can be None to indicate no revisions, not just NULL_REVISION
791
if revid is None or revision.is_null(revid):
795
if committers is None or not committers:
796
fmt_committers = 'no'
798
fmt_committers = 'yes'
799
response_tuple, response_handler = self._call_expecting_body(
800
'Repository.gather_stats', path, fmt_revid, fmt_committers)
801
if response_tuple[0] != 'ok':
802
raise errors.UnexpectedSmartServerResponse(response_tuple)
804
body = response_handler.read_body_bytes()
806
for line in body.split('\n'):
809
key, val_text = line.split(':')
810
if key in ('revisions', 'size', 'committers'):
811
result[key] = int(val_text)
812
elif key in ('firstrev', 'latestrev'):
813
values = val_text.split(' ')[1:]
814
result[key] = (float(values[0]), long(values[1]))
818
def find_branches(self, using=False):
819
"""See Repository.find_branches()."""
820
# should be an API call to the server.
822
return self._real_repository.find_branches(using=using)
824
def get_physical_lock_status(self):
825
"""See Repository.get_physical_lock_status()."""
826
# should be an API call to the server.
828
return self._real_repository.get_physical_lock_status()
830
def is_in_write_group(self):
831
"""Return True if there is an open write group.
833
write groups are only applicable locally for the smart server..
835
if self._real_repository:
836
return self._real_repository.is_in_write_group()
839
return self._lock_count >= 1
842
"""See Repository.is_shared()."""
843
path = self.bzrdir._path_for_remote_call(self._client)
844
response = self._call('Repository.is_shared', path)
845
if response[0] not in ('yes', 'no'):
846
raise SmartProtocolError('unexpected response code %s' % (response,))
847
return response[0] == 'yes'
849
def is_write_locked(self):
850
return self._lock_mode == 'w'
853
# wrong eventually - want a local lock cache context
854
if not self._lock_mode:
855
self._lock_mode = 'r'
857
self._unstacked_provider.enable_cache(cache_misses=True)
858
if self._real_repository is not None:
859
self._real_repository.lock_read()
861
self._lock_count += 1
862
for repo in self._fallback_repositories:
865
def _remote_lock_write(self, token):
866
path = self.bzrdir._path_for_remote_call(self._client)
869
err_context = {'token': token}
870
response = self._call('Repository.lock_write', path, token,
872
if response[0] == 'ok':
876
raise errors.UnexpectedSmartServerResponse(response)
878
def lock_write(self, token=None, _skip_rpc=False):
879
if not self._lock_mode:
881
if self._lock_token is not None:
882
if token != self._lock_token:
883
raise errors.TokenMismatch(token, self._lock_token)
884
self._lock_token = token
886
self._lock_token = self._remote_lock_write(token)
887
# if self._lock_token is None, then this is something like packs or
888
# svn where we don't get to lock the repo, or a weave style repository
889
# where we cannot lock it over the wire and attempts to do so will
891
if self._real_repository is not None:
892
self._real_repository.lock_write(token=self._lock_token)
893
if token is not None:
894
self._leave_lock = True
896
self._leave_lock = False
897
self._lock_mode = 'w'
899
self._unstacked_provider.enable_cache(cache_misses=False)
900
elif self._lock_mode == 'r':
901
raise errors.ReadOnlyError(self)
903
self._lock_count += 1
904
for repo in self._fallback_repositories:
905
# Writes don't affect fallback repos
907
return self._lock_token or None
909
def leave_lock_in_place(self):
910
if not self._lock_token:
911
raise NotImplementedError(self.leave_lock_in_place)
912
self._leave_lock = True
914
def dont_leave_lock_in_place(self):
915
if not self._lock_token:
916
raise NotImplementedError(self.dont_leave_lock_in_place)
917
self._leave_lock = False
919
def _set_real_repository(self, repository):
920
"""Set the _real_repository for this repository.
922
:param repository: The repository to fallback to for non-hpss
923
implemented operations.
925
if self._real_repository is not None:
926
# Replacing an already set real repository.
927
# We cannot do this [currently] if the repository is locked -
928
# synchronised state might be lost.
930
raise AssertionError('_real_repository is already set')
931
if isinstance(repository, RemoteRepository):
932
raise AssertionError()
933
self._real_repository = repository
934
# three code paths happen here:
935
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
936
# up stacking. In this case self._fallback_repositories is [], and the
937
# real repo is already setup. Preserve the real repo and
938
# RemoteRepository.add_fallback_repository will avoid adding
940
# 2) new servers, RemoteBranch.open() sets up stacking, and when
941
# ensure_real is triggered from a branch, the real repository to
942
# set already has a matching list with separate instances, but
943
# as they are also RemoteRepositories we don't worry about making the
944
# lists be identical.
945
# 3) new servers, RemoteRepository.ensure_real is triggered before
946
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
947
# and need to populate it.
948
if (self._fallback_repositories and
949
len(self._real_repository._fallback_repositories) !=
950
len(self._fallback_repositories)):
951
if len(self._real_repository._fallback_repositories):
952
raise AssertionError(
953
"cannot cleanly remove existing _fallback_repositories")
954
for fb in self._fallback_repositories:
955
self._real_repository.add_fallback_repository(fb)
956
if self._lock_mode == 'w':
957
# if we are already locked, the real repository must be able to
958
# acquire the lock with our token.
959
self._real_repository.lock_write(self._lock_token)
960
elif self._lock_mode == 'r':
961
self._real_repository.lock_read()
963
def start_write_group(self):
964
"""Start a write group on the decorated repository.
966
Smart methods perform operations in a single step so this API
967
is not really applicable except as a compatibility thunk
968
for older plugins that don't use e.g. the CommitBuilder
972
return self._real_repository.start_write_group()
974
def _unlock(self, token):
975
path = self.bzrdir._path_for_remote_call(self._client)
977
# with no token the remote repository is not persistently locked.
979
err_context = {'token': token}
980
response = self._call('Repository.unlock', path, token,
982
if response == ('ok',):
985
raise errors.UnexpectedSmartServerResponse(response)
988
if not self._lock_count:
989
raise errors.LockNotHeld(self)
990
self._lock_count -= 1
991
if self._lock_count > 0:
993
self._unstacked_provider.disable_cache()
994
old_mode = self._lock_mode
995
self._lock_mode = None
997
# The real repository is responsible at present for raising an
998
# exception if it's in an unfinished write group. However, it
999
# normally will *not* actually remove the lock from disk - that's
1000
# done by the server on receiving the Repository.unlock call.
1001
# This is just to let the _real_repository stay up to date.
1002
if self._real_repository is not None:
1003
self._real_repository.unlock()
1005
# The rpc-level lock should be released even if there was a
1006
# problem releasing the vfs-based lock.
1008
# Only write-locked repositories need to make a remote method
1009
# call to perform the unlock.
1010
old_token = self._lock_token
1011
self._lock_token = None
1012
if not self._leave_lock:
1013
self._unlock(old_token)
1015
def break_lock(self):
1016
# should hand off to the network
1018
return self._real_repository.break_lock()
1020
def _get_tarball(self, compression):
1021
"""Return a TemporaryFile containing a repository tarball.
1023
Returns None if the server does not support sending tarballs.
1026
path = self.bzrdir._path_for_remote_call(self._client)
1028
response, protocol = self._call_expecting_body(
1029
'Repository.tarball', path, compression)
1030
except errors.UnknownSmartMethod:
1031
protocol.cancel_read_body()
1033
if response[0] == 'ok':
1034
# Extract the tarball and return it
1035
t = tempfile.NamedTemporaryFile()
1036
# TODO: rpc layer should read directly into it...
1037
t.write(protocol.read_body_bytes())
1040
raise errors.UnexpectedSmartServerResponse(response)
1042
def sprout(self, to_bzrdir, revision_id=None):
1043
# TODO: Option to control what format is created?
1045
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1047
dest_repo.fetch(self, revision_id=revision_id)
1050
### These methods are just thin shims to the VFS object for now.
1052
def revision_tree(self, revision_id):
1054
return self._real_repository.revision_tree(revision_id)
1056
def get_serializer_format(self):
1058
return self._real_repository.get_serializer_format()
1060
def get_commit_builder(self, branch, parents, config, timestamp=None,
1061
timezone=None, committer=None, revprops=None,
1063
# FIXME: It ought to be possible to call this without immediately
1064
# triggering _ensure_real. For now it's the easiest thing to do.
1066
real_repo = self._real_repository
1067
builder = real_repo.get_commit_builder(branch, parents,
1068
config, timestamp=timestamp, timezone=timezone,
1069
committer=committer, revprops=revprops, revision_id=revision_id)
1072
def add_fallback_repository(self, repository):
1073
"""Add a repository to use for looking up data not held locally.
1075
:param repository: A repository.
1077
if not self._format.supports_external_lookups:
1078
raise errors.UnstackableRepositoryFormat(
1079
self._format.network_name(), self.base)
1080
# We need to accumulate additional repositories here, to pass them in
1083
self._fallback_repositories.append(repository)
1084
# If self._real_repository was parameterised already (e.g. because a
1085
# _real_branch had its get_stacked_on_url method called), then the
1086
# repository to be added may already be in the _real_repositories list.
1087
if self._real_repository is not None:
1088
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1089
self._real_repository._fallback_repositories]
1090
if repository.bzrdir.root_transport.base not in fallback_locations:
1091
self._real_repository.add_fallback_repository(repository)
1093
def add_inventory(self, revid, inv, parents):
1095
return self._real_repository.add_inventory(revid, inv, parents)
1097
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1100
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1101
delta, new_revision_id, parents)
1103
def add_revision(self, rev_id, rev, inv=None, config=None):
1105
return self._real_repository.add_revision(
1106
rev_id, rev, inv=inv, config=config)
1109
def get_inventory(self, revision_id):
1111
return self._real_repository.get_inventory(revision_id)
1113
def iter_inventories(self, revision_ids):
1115
return self._real_repository.iter_inventories(revision_ids)
1118
def get_revision(self, revision_id):
1120
return self._real_repository.get_revision(revision_id)
1122
def get_transaction(self):
1124
return self._real_repository.get_transaction()
1127
def clone(self, a_bzrdir, revision_id=None):
1129
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1131
def make_working_trees(self):
1132
"""See Repository.make_working_trees"""
1134
return self._real_repository.make_working_trees()
1136
def refresh_data(self):
1137
"""Re-read any data needed to to synchronise with disk.
1139
This method is intended to be called after another repository instance
1140
(such as one used by a smart server) has inserted data into the
1141
repository. It may not be called during a write group, but may be
1142
called at any other time.
1144
if self.is_in_write_group():
1145
raise errors.InternalBzrError(
1146
"May not refresh_data while in a write group.")
1147
if self._real_repository is not None:
1148
self._real_repository.refresh_data()
1150
def revision_ids_to_search_result(self, result_set):
1151
"""Convert a set of revision ids to a graph SearchResult."""
1152
result_parents = set()
1153
for parents in self.get_graph().get_parent_map(
1154
result_set).itervalues():
1155
result_parents.update(parents)
1156
included_keys = result_set.intersection(result_parents)
1157
start_keys = result_set.difference(included_keys)
1158
exclude_keys = result_parents.difference(result_set)
1159
result = graph.SearchResult(start_keys, exclude_keys,
1160
len(result_set), result_set)
1164
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1165
"""Return the revision ids that other has that this does not.
1167
These are returned in topological order.
1169
revision_id: only return revision ids included by revision_id.
1171
return repository.InterRepository.get(
1172
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1174
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1176
# No base implementation to use as RemoteRepository is not a subclass
1177
# of Repository; so this is a copy of Repository.fetch().
1178
if fetch_spec is not None and revision_id is not None:
1179
raise AssertionError(
1180
"fetch_spec and revision_id are mutually exclusive.")
1181
if self.is_in_write_group():
1182
raise errors.InternalBzrError(
1183
"May not fetch while in a write group.")
1184
# fast path same-url fetch operations
1185
if self.has_same_location(source) and fetch_spec is None:
1186
# check that last_revision is in 'from' and then return a
1188
if (revision_id is not None and
1189
not revision.is_null(revision_id)):
1190
self.get_revision(revision_id)
1192
# if there is no specific appropriate InterRepository, this will get
1193
# the InterRepository base class, which raises an
1194
# IncompatibleRepositories when asked to fetch.
1195
inter = repository.InterRepository.get(source, self)
1196
return inter.fetch(revision_id=revision_id, pb=pb,
1197
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1199
def create_bundle(self, target, base, fileobj, format=None):
1201
self._real_repository.create_bundle(target, base, fileobj, format)
1204
def get_ancestry(self, revision_id, topo_sorted=True):
1206
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1208
def fileids_altered_by_revision_ids(self, revision_ids):
1210
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1212
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1214
return self._real_repository._get_versioned_file_checker(
1215
revisions, revision_versions_cache)
1217
def iter_files_bytes(self, desired_files):
1218
"""See Repository.iter_file_bytes.
1221
return self._real_repository.iter_files_bytes(desired_files)
1223
def get_parent_map(self, revision_ids):
1224
"""See bzrlib.Graph.get_parent_map()."""
1225
return self._make_parents_provider().get_parent_map(revision_ids)
1227
def _get_parent_map_rpc(self, keys):
1228
"""Helper for get_parent_map that performs the RPC."""
1229
medium = self._client._medium
1230
if medium._is_remote_before((1, 2)):
1231
# We already found out that the server can't understand
1232
# Repository.get_parent_map requests, so just fetch the whole
1235
# Note that this reads the whole graph, when only some keys are
1236
# wanted. On this old server there's no way (?) to get them all
1237
# in one go, and the user probably will have seen a warning about
1238
# the server being old anyhow.
1239
rg = self._get_revision_graph(None)
1240
# There is an API discrepancy between get_parent_map and
1241
# get_revision_graph. Specifically, a "key:()" pair in
1242
# get_revision_graph just means a node has no parents. For
1243
# "get_parent_map" it means the node is a ghost. So fix up the
1244
# graph to correct this.
1245
# https://bugs.launchpad.net/bzr/+bug/214894
1246
# There is one other "bug" which is that ghosts in
1247
# get_revision_graph() are not returned at all. But we won't worry
1248
# about that for now.
1249
for node_id, parent_ids in rg.iteritems():
1250
if parent_ids == ():
1251
rg[node_id] = (NULL_REVISION,)
1252
rg[NULL_REVISION] = ()
1257
raise ValueError('get_parent_map(None) is not valid')
1258
if NULL_REVISION in keys:
1259
keys.discard(NULL_REVISION)
1260
found_parents = {NULL_REVISION:()}
1262
return found_parents
1265
# TODO(Needs analysis): We could assume that the keys being requested
1266
# from get_parent_map are in a breadth first search, so typically they
1267
# will all be depth N from some common parent, and we don't have to
1268
# have the server iterate from the root parent, but rather from the
1269
# keys we're searching; and just tell the server the keyspace we
1270
# already have; but this may be more traffic again.
1272
# Transform self._parents_map into a search request recipe.
1273
# TODO: Manage this incrementally to avoid covering the same path
1274
# repeatedly. (The server will have to on each request, but the less
1275
# work done the better).
1277
# Negative caching notes:
1278
# new server sends missing when a request including the revid
1279
# 'include-missing:' is present in the request.
1280
# missing keys are serialised as missing:X, and we then call
1281
# provider.note_missing(X) for-all X
1282
parents_map = self._unstacked_provider.get_cached_map()
1283
if parents_map is None:
1284
# Repository is not locked, so there's no cache.
1286
# start_set is all the keys in the cache
1287
start_set = set(parents_map)
1288
# result set is all the references to keys in the cache
1289
result_parents = set()
1290
for parents in parents_map.itervalues():
1291
result_parents.update(parents)
1292
stop_keys = result_parents.difference(start_set)
1293
# We don't need to send ghosts back to the server as a position to
1295
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1296
key_count = len(parents_map)
1297
if (NULL_REVISION in result_parents
1298
and NULL_REVISION in self._unstacked_provider.missing_keys):
1299
# If we pruned NULL_REVISION from the stop_keys because it's also
1300
# in our cache of "missing" keys we need to increment our key count
1301
# by 1, because the reconsitituted SearchResult on the server will
1302
# still consider NULL_REVISION to be an included key.
1304
included_keys = start_set.intersection(result_parents)
1305
start_set.difference_update(included_keys)
1306
recipe = ('manual', start_set, stop_keys, key_count)
1307
body = self._serialise_search_recipe(recipe)
1308
path = self.bzrdir._path_for_remote_call(self._client)
1310
if type(key) is not str:
1312
"key %r not a plain string" % (key,))
1313
verb = 'Repository.get_parent_map'
1314
args = (path, 'include-missing:') + tuple(keys)
1316
response = self._call_with_body_bytes_expecting_body(
1318
except errors.UnknownSmartMethod:
1319
# Server does not support this method, so get the whole graph.
1320
# Worse, we have to force a disconnection, because the server now
1321
# doesn't realise it has a body on the wire to consume, so the
1322
# only way to recover is to abandon the connection.
1324
'Server is too old for fast get_parent_map, reconnecting. '
1325
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1327
# To avoid having to disconnect repeatedly, we keep track of the
1328
# fact the server doesn't understand remote methods added in 1.2.
1329
medium._remember_remote_is_before((1, 2))
1330
# Recurse just once and we should use the fallback code.
1331
return self._get_parent_map_rpc(keys)
1332
response_tuple, response_handler = response
1333
if response_tuple[0] not in ['ok']:
1334
response_handler.cancel_read_body()
1335
raise errors.UnexpectedSmartServerResponse(response_tuple)
1336
if response_tuple[0] == 'ok':
1337
coded = bz2.decompress(response_handler.read_body_bytes())
1339
# no revisions found
1341
lines = coded.split('\n')
1344
d = tuple(line.split())
1346
revision_graph[d[0]] = d[1:]
1349
if d[0].startswith('missing:'):
1351
self._unstacked_provider.note_missing_key(revid)
1353
# no parents - so give the Graph result
1355
revision_graph[d[0]] = (NULL_REVISION,)
1356
return revision_graph
1359
def get_signature_text(self, revision_id):
1361
return self._real_repository.get_signature_text(revision_id)
1364
def get_inventory_xml(self, revision_id):
1366
return self._real_repository.get_inventory_xml(revision_id)
1368
def deserialise_inventory(self, revision_id, xml):
1370
return self._real_repository.deserialise_inventory(revision_id, xml)
1372
def reconcile(self, other=None, thorough=False):
1374
return self._real_repository.reconcile(other=other, thorough=thorough)
1376
def all_revision_ids(self):
1378
return self._real_repository.all_revision_ids()
1381
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1383
return self._real_repository.get_deltas_for_revisions(revisions,
1384
specific_fileids=specific_fileids)
1387
def get_revision_delta(self, revision_id, specific_fileids=None):
1389
return self._real_repository.get_revision_delta(revision_id,
1390
specific_fileids=specific_fileids)
1393
def revision_trees(self, revision_ids):
1395
return self._real_repository.revision_trees(revision_ids)
1398
def get_revision_reconcile(self, revision_id):
1400
return self._real_repository.get_revision_reconcile(revision_id)
1403
def check(self, revision_ids=None):
1405
return self._real_repository.check(revision_ids=revision_ids)
1407
def copy_content_into(self, destination, revision_id=None):
1409
return self._real_repository.copy_content_into(
1410
destination, revision_id=revision_id)
1412
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1413
# get a tarball of the remote repository, and copy from that into the
1415
from bzrlib import osutils
1417
# TODO: Maybe a progress bar while streaming the tarball?
1418
note("Copying repository content as tarball...")
1419
tar_file = self._get_tarball('bz2')
1420
if tar_file is None:
1422
destination = to_bzrdir.create_repository()
1424
tar = tarfile.open('repository', fileobj=tar_file,
1426
tmpdir = osutils.mkdtemp()
1428
_extract_tar(tar, tmpdir)
1429
tmp_bzrdir = BzrDir.open(tmpdir)
1430
tmp_repo = tmp_bzrdir.open_repository()
1431
tmp_repo.copy_content_into(destination, revision_id)
1433
osutils.rmtree(tmpdir)
1437
# TODO: Suggestion from john: using external tar is much faster than
1438
# python's tarfile library, but it may not work on windows.
1441
def inventories(self):
1442
"""Decorate the real repository for now.
1444
In the long term a full blown network facility is needed to
1445
avoid creating a real repository object locally.
1448
return self._real_repository.inventories
1452
"""Compress the data within the repository.
1454
This is not currently implemented within the smart server.
1457
return self._real_repository.pack()
1460
def revisions(self):
1461
"""Decorate the real repository for now.
1463
In the short term this should become a real object to intercept graph
1466
In the long term a full blown network facility is needed.
1469
return self._real_repository.revisions
1471
def set_make_working_trees(self, new_value):
1473
new_value_str = "True"
1475
new_value_str = "False"
1476
path = self.bzrdir._path_for_remote_call(self._client)
1478
response = self._call(
1479
'Repository.set_make_working_trees', path, new_value_str)
1480
except errors.UnknownSmartMethod:
1482
self._real_repository.set_make_working_trees(new_value)
1484
if response[0] != 'ok':
1485
raise errors.UnexpectedSmartServerResponse(response)
1488
def signatures(self):
1489
"""Decorate the real repository for now.
1491
In the long term a full blown network facility is needed to avoid
1492
creating a real repository object locally.
1495
return self._real_repository.signatures
1498
def sign_revision(self, revision_id, gpg_strategy):
1500
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1504
"""Decorate the real repository for now.
1506
In the long term a full blown network facility is needed to avoid
1507
creating a real repository object locally.
1510
return self._real_repository.texts
1513
def get_revisions(self, revision_ids):
1515
return self._real_repository.get_revisions(revision_ids)
1517
def supports_rich_root(self):
1518
return self._format.rich_root_data
1520
def iter_reverse_revision_history(self, revision_id):
1522
return self._real_repository.iter_reverse_revision_history(revision_id)
1525
def _serializer(self):
1526
return self._format._serializer
1528
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1530
return self._real_repository.store_revision_signature(
1531
gpg_strategy, plaintext, revision_id)
1533
def add_signature_text(self, revision_id, signature):
1535
return self._real_repository.add_signature_text(revision_id, signature)
1537
def has_signature_for_revision_id(self, revision_id):
1539
return self._real_repository.has_signature_for_revision_id(revision_id)
1541
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1543
return self._real_repository.item_keys_introduced_by(revision_ids,
1544
_files_pb=_files_pb)
1546
def revision_graph_can_have_wrong_parents(self):
1547
# The answer depends on the remote repo format.
1549
return self._real_repository.revision_graph_can_have_wrong_parents()
1551
def _find_inconsistent_revision_parents(self):
1553
return self._real_repository._find_inconsistent_revision_parents()
1555
def _check_for_inconsistent_revision_parents(self):
1557
return self._real_repository._check_for_inconsistent_revision_parents()
1559
def _make_parents_provider(self, other=None):
1560
providers = [self._unstacked_provider]
1561
if other is not None:
1562
providers.insert(0, other)
1563
providers.extend(r._make_parents_provider() for r in
1564
self._fallback_repositories)
1565
return graph._StackedParentsProvider(providers)
1567
def _serialise_search_recipe(self, recipe):
1568
"""Serialise a graph search recipe.
1570
:param recipe: A search recipe (start, stop, count).
1571
:return: Serialised bytes.
1573
start_keys = ' '.join(recipe[1])
1574
stop_keys = ' '.join(recipe[2])
1575
count = str(recipe[3])
1576
return '\n'.join((start_keys, stop_keys, count))
1578
def _serialise_search_result(self, search_result):
1579
if isinstance(search_result, graph.PendingAncestryResult):
1580
parts = ['ancestry-of']
1581
parts.extend(search_result.heads)
1583
recipe = search_result.get_recipe()
1584
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1585
return '\n'.join(parts)
1588
path = self.bzrdir._path_for_remote_call(self._client)
1590
response = self._call('PackRepository.autopack', path)
1591
except errors.UnknownSmartMethod:
1593
self._real_repository._pack_collection.autopack()
1596
if response[0] != 'ok':
1597
raise errors.UnexpectedSmartServerResponse(response)
1600
class RemoteStreamSink(repository.StreamSink):
1602
def _insert_real(self, stream, src_format, resume_tokens):
1603
self.target_repo._ensure_real()
1604
sink = self.target_repo._real_repository._get_sink()
1605
result = sink.insert_stream(stream, src_format, resume_tokens)
1607
self.target_repo.autopack()
1610
def insert_stream(self, stream, src_format, resume_tokens):
1611
target = self.target_repo
1612
if target._lock_token:
1613
verb = 'Repository.insert_stream_locked'
1614
extra_args = (target._lock_token or '',)
1615
required_version = (1, 14)
1617
verb = 'Repository.insert_stream'
1619
required_version = (1, 13)
1620
client = target._client
1621
medium = client._medium
1622
if medium._is_remote_before(required_version):
1623
# No possible way this can work.
1624
return self._insert_real(stream, src_format, resume_tokens)
1625
path = target.bzrdir._path_for_remote_call(client)
1626
if not resume_tokens:
1627
# XXX: Ugly but important for correctness, *will* be fixed during
1628
# 1.13 cycle. Pushing a stream that is interrupted results in a
1629
# fallback to the _real_repositories sink *with a partial stream*.
1630
# Thats bad because we insert less data than bzr expected. To avoid
1631
# this we do a trial push to make sure the verb is accessible, and
1632
# do not fallback when actually pushing the stream. A cleanup patch
1633
# is going to look at rewinding/restarting the stream/partial
1635
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1637
response = client.call_with_body_stream(
1638
(verb, path, '') + extra_args, byte_stream)
1639
except errors.UnknownSmartMethod:
1640
medium._remember_remote_is_before(required_version)
1641
return self._insert_real(stream, src_format, resume_tokens)
1642
byte_stream = smart_repo._stream_to_byte_stream(
1644
resume_tokens = ' '.join(resume_tokens)
1645
response = client.call_with_body_stream(
1646
(verb, path, resume_tokens) + extra_args, byte_stream)
1647
if response[0][0] not in ('ok', 'missing-basis'):
1648
raise errors.UnexpectedSmartServerResponse(response)
1649
if response[0][0] == 'missing-basis':
1650
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1651
resume_tokens = tokens
1652
return resume_tokens, set(missing_keys)
1654
self.target_repo.refresh_data()
1658
class RemoteStreamSource(repository.StreamSource):
1659
"""Stream data from a remote server."""
1661
def get_stream(self, search):
1662
if (self.from_repository._fallback_repositories and
1663
self.to_format._fetch_order == 'topological'):
1664
return self._real_stream(self.from_repository, search)
1665
return self.missing_parents_chain(search, [self.from_repository] +
1666
self.from_repository._fallback_repositories)
1668
def _real_stream(self, repo, search):
1669
"""Get a stream for search from repo.
1671
This never called RemoteStreamSource.get_stream, and is a heler
1672
for RemoteStreamSource._get_stream to allow getting a stream
1673
reliably whether fallback back because of old servers or trying
1674
to stream from a non-RemoteRepository (which the stacked support
1677
source = repo._get_source(self.to_format)
1678
if isinstance(source, RemoteStreamSource):
1679
return repository.StreamSource.get_stream(source, search)
1680
return source.get_stream(search)
1682
def _get_stream(self, repo, search):
1683
"""Core worker to get a stream from repo for search.
1685
This is used by both get_stream and the stacking support logic. It
1686
deliberately gets a stream for repo which does not need to be
1687
self.from_repository. In the event that repo is not Remote, or
1688
cannot do a smart stream, a fallback is made to the generic
1689
repository._get_stream() interface, via self._real_stream.
1691
In the event of stacking, streams from _get_stream will not
1692
contain all the data for search - this is normal (see get_stream).
1694
:param repo: A repository.
1695
:param search: A search.
1697
# Fallbacks may be non-smart
1698
if not isinstance(repo, RemoteRepository):
1699
return self._real_stream(repo, search)
1700
client = repo._client
1701
medium = client._medium
1702
if medium._is_remote_before((1, 13)):
1703
# streaming was added in 1.13
1704
return self._real_stream(repo, search)
1705
path = repo.bzrdir._path_for_remote_call(client)
1707
search_bytes = repo._serialise_search_result(search)
1708
response = repo._call_with_body_bytes_expecting_body(
1709
'Repository.get_stream',
1710
(path, self.to_format.network_name()), search_bytes)
1711
response_tuple, response_handler = response
1712
except errors.UnknownSmartMethod:
1713
medium._remember_remote_is_before((1,13))
1714
return self._real_stream(repo, search)
1715
if response_tuple[0] != 'ok':
1716
raise errors.UnexpectedSmartServerResponse(response_tuple)
1717
byte_stream = response_handler.read_streamed_body()
1718
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1719
if src_format.network_name() != repo._format.network_name():
1720
raise AssertionError(
1721
"Mismatched RemoteRepository and stream src %r, %r" % (
1722
src_format.network_name(), repo._format.network_name()))
1725
def missing_parents_chain(self, search, sources):
1726
"""Chain multiple streams together to handle stacking.
1728
:param search: The overall search to satisfy with streams.
1729
:param sources: A list of Repository objects to query.
1731
self.serialiser = self.to_format._serializer
1732
self.seen_revs = set()
1733
self.referenced_revs = set()
1734
# If there are heads in the search, or the key count is > 0, we are not
1736
while not search.is_empty() and len(sources) > 1:
1737
source = sources.pop(0)
1738
stream = self._get_stream(source, search)
1739
for kind, substream in stream:
1740
if kind != 'revisions':
1741
yield kind, substream
1743
yield kind, self.missing_parents_rev_handler(substream)
1744
search = search.refine(self.seen_revs, self.referenced_revs)
1745
self.seen_revs = set()
1746
self.referenced_revs = set()
1747
if not search.is_empty():
1748
for kind, stream in self._get_stream(sources[0], search):
1751
def missing_parents_rev_handler(self, substream):
1752
for content in substream:
1753
revision_bytes = content.get_bytes_as('fulltext')
1754
revision = self.serialiser.read_revision_from_string(revision_bytes)
1755
self.seen_revs.add(content.key[-1])
1756
self.referenced_revs.update(revision.parent_ids)
1760
class RemoteBranchLockableFiles(LockableFiles):
1761
"""A 'LockableFiles' implementation that talks to a smart server.
1763
This is not a public interface class.
1766
def __init__(self, bzrdir, _client):
1767
self.bzrdir = bzrdir
1768
self._client = _client
1769
self._need_find_modes = True
1770
LockableFiles.__init__(
1771
self, bzrdir.get_branch_transport(None),
1772
'lock', lockdir.LockDir)
1774
def _find_modes(self):
1775
# RemoteBranches don't let the client set the mode of control files.
1776
self._dir_mode = None
1777
self._file_mode = None
1780
class RemoteBranchFormat(branch.BranchFormat):
1782
def __init__(self, network_name=None):
1783
super(RemoteBranchFormat, self).__init__()
1784
self._matchingbzrdir = RemoteBzrDirFormat()
1785
self._matchingbzrdir.set_branch_format(self)
1786
self._custom_format = None
1787
self._network_name = network_name
1789
def __eq__(self, other):
1790
return (isinstance(other, RemoteBranchFormat) and
1791
self.__dict__ == other.__dict__)
1793
def _ensure_real(self):
1794
if self._custom_format is None:
1795
self._custom_format = branch.network_format_registry.get(
1798
def get_format_description(self):
1799
return 'Remote BZR Branch'
1801
def network_name(self):
1802
return self._network_name
1804
def open(self, a_bzrdir, ignore_fallbacks=False):
1805
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1807
def _vfs_initialize(self, a_bzrdir):
1808
# Initialisation when using a local bzrdir object, or a non-vfs init
1809
# method is not available on the server.
1810
# self._custom_format is always set - the start of initialize ensures
1812
if isinstance(a_bzrdir, RemoteBzrDir):
1813
a_bzrdir._ensure_real()
1814
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1816
# We assume the bzrdir is parameterised; it may not be.
1817
result = self._custom_format.initialize(a_bzrdir)
1818
if (isinstance(a_bzrdir, RemoteBzrDir) and
1819
not isinstance(result, RemoteBranch)):
1820
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1823
def initialize(self, a_bzrdir):
1824
# 1) get the network name to use.
1825
if self._custom_format:
1826
network_name = self._custom_format.network_name()
1828
# Select the current bzrlib default and ask for that.
1829
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1830
reference_format = reference_bzrdir_format.get_branch_format()
1831
self._custom_format = reference_format
1832
network_name = reference_format.network_name()
1833
# Being asked to create on a non RemoteBzrDir:
1834
if not isinstance(a_bzrdir, RemoteBzrDir):
1835
return self._vfs_initialize(a_bzrdir)
1836
medium = a_bzrdir._client._medium
1837
if medium._is_remote_before((1, 13)):
1838
return self._vfs_initialize(a_bzrdir)
1839
# Creating on a remote bzr dir.
1840
# 2) try direct creation via RPC
1841
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1842
verb = 'BzrDir.create_branch'
1844
response = a_bzrdir._call(verb, path, network_name)
1845
except errors.UnknownSmartMethod:
1846
# Fallback - use vfs methods
1847
medium._remember_remote_is_before((1, 13))
1848
return self._vfs_initialize(a_bzrdir)
1849
if response[0] != 'ok':
1850
raise errors.UnexpectedSmartServerResponse(response)
1851
# Turn the response into a RemoteRepository object.
1852
format = RemoteBranchFormat(network_name=response[1])
1853
repo_format = response_tuple_to_repo_format(response[3:])
1854
if response[2] == '':
1855
repo_bzrdir = a_bzrdir
1857
repo_bzrdir = RemoteBzrDir(
1858
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1860
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1861
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1862
format=format, setup_stacking=False)
1863
# XXX: We know this is a new branch, so it must have revno 0, revid
1864
# NULL_REVISION. Creating the branch locked would make this be unable
1865
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1866
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1867
return remote_branch
1869
def make_tags(self, branch):
1871
return self._custom_format.make_tags(branch)
1873
def supports_tags(self):
1874
# Remote branches might support tags, but we won't know until we
1875
# access the real remote branch.
1877
return self._custom_format.supports_tags()
1879
def supports_stacking(self):
1881
return self._custom_format.supports_stacking()
1884
class RemoteBranch(branch.Branch, _RpcHelper):
1885
"""Branch stored on a server accessed by HPSS RPC.
1887
At the moment most operations are mapped down to simple file operations.
1890
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1891
_client=None, format=None, setup_stacking=True):
1892
"""Create a RemoteBranch instance.
1894
:param real_branch: An optional local implementation of the branch
1895
format, usually accessing the data via the VFS.
1896
:param _client: Private parameter for testing.
1897
:param format: A RemoteBranchFormat object, None to create one
1898
automatically. If supplied it should have a network_name already
1900
:param setup_stacking: If True make an RPC call to determine the
1901
stacked (or not) status of the branch. If False assume the branch
1904
# We intentionally don't call the parent class's __init__, because it
1905
# will try to assign to self.tags, which is a property in this subclass.
1906
# And the parent's __init__ doesn't do much anyway.
1907
self._revision_id_to_revno_cache = None
1908
self._partial_revision_id_to_revno_cache = {}
1909
self._revision_history_cache = None
1910
self._last_revision_info_cache = None
1911
self._merge_sorted_revisions_cache = None
1912
self.bzrdir = remote_bzrdir
1913
if _client is not None:
1914
self._client = _client
1916
self._client = remote_bzrdir._client
1917
self.repository = remote_repository
1918
if real_branch is not None:
1919
self._real_branch = real_branch
1920
# Give the remote repository the matching real repo.
1921
real_repo = self._real_branch.repository
1922
if isinstance(real_repo, RemoteRepository):
1923
real_repo._ensure_real()
1924
real_repo = real_repo._real_repository
1925
self.repository._set_real_repository(real_repo)
1926
# Give the branch the remote repository to let fast-pathing happen.
1927
self._real_branch.repository = self.repository
1929
self._real_branch = None
1930
# Fill out expected attributes of branch for bzrlib API users.
1931
self.base = self.bzrdir.root_transport.base
1932
self._control_files = None
1933
self._lock_mode = None
1934
self._lock_token = None
1935
self._repo_lock_token = None
1936
self._lock_count = 0
1937
self._leave_lock = False
1938
# Setup a format: note that we cannot call _ensure_real until all the
1939
# attributes above are set: This code cannot be moved higher up in this
1942
self._format = RemoteBranchFormat()
1943
if real_branch is not None:
1944
self._format._network_name = \
1945
self._real_branch._format.network_name()
1947
self._format = format
1948
if not self._format._network_name:
1949
# Did not get from open_branchV2 - old server.
1951
self._format._network_name = \
1952
self._real_branch._format.network_name()
1953
self.tags = self._format.make_tags(self)
1954
# The base class init is not called, so we duplicate this:
1955
hooks = branch.Branch.hooks['open']
1959
self._setup_stacking()
1961
def _setup_stacking(self):
1962
# configure stacking into the remote repository, by reading it from
1965
fallback_url = self.get_stacked_on_url()
1966
except (errors.NotStacked, errors.UnstackableBranchFormat,
1967
errors.UnstackableRepositoryFormat), e:
1969
self._activate_fallback_location(fallback_url)
1971
def _get_config(self):
1972
return RemoteBranchConfig(self)
1974
def _get_real_transport(self):
1975
# if we try vfs access, return the real branch's vfs transport
1977
return self._real_branch._transport
1979
_transport = property(_get_real_transport)
1982
return "%s(%s)" % (self.__class__.__name__, self.base)
1986
def _ensure_real(self):
1987
"""Ensure that there is a _real_branch set.
1989
Used before calls to self._real_branch.
1991
if self._real_branch is None:
1992
if not vfs.vfs_enabled():
1993
raise AssertionError('smart server vfs must be enabled '
1994
'to use vfs implementation')
1995
self.bzrdir._ensure_real()
1996
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1997
if self.repository._real_repository is None:
1998
# Give the remote repository the matching real repo.
1999
real_repo = self._real_branch.repository
2000
if isinstance(real_repo, RemoteRepository):
2001
real_repo._ensure_real()
2002
real_repo = real_repo._real_repository
2003
self.repository._set_real_repository(real_repo)
2004
# Give the real branch the remote repository to let fast-pathing
2006
self._real_branch.repository = self.repository
2007
if self._lock_mode == 'r':
2008
self._real_branch.lock_read()
2009
elif self._lock_mode == 'w':
2010
self._real_branch.lock_write(token=self._lock_token)
2012
def _translate_error(self, err, **context):
2013
self.repository._translate_error(err, branch=self, **context)
2015
def _clear_cached_state(self):
2016
super(RemoteBranch, self)._clear_cached_state()
2017
if self._real_branch is not None:
2018
self._real_branch._clear_cached_state()
2020
def _clear_cached_state_of_remote_branch_only(self):
2021
"""Like _clear_cached_state, but doesn't clear the cache of
2024
This is useful when falling back to calling a method of
2025
self._real_branch that changes state. In that case the underlying
2026
branch changes, so we need to invalidate this RemoteBranch's cache of
2027
it. However, there's no need to invalidate the _real_branch's cache
2028
too, in fact doing so might harm performance.
2030
super(RemoteBranch, self)._clear_cached_state()
2033
def control_files(self):
2034
# Defer actually creating RemoteBranchLockableFiles until its needed,
2035
# because it triggers an _ensure_real that we otherwise might not need.
2036
if self._control_files is None:
2037
self._control_files = RemoteBranchLockableFiles(
2038
self.bzrdir, self._client)
2039
return self._control_files
2041
def _get_checkout_format(self):
2043
return self._real_branch._get_checkout_format()
2045
def get_physical_lock_status(self):
2046
"""See Branch.get_physical_lock_status()."""
2047
# should be an API call to the server, as branches must be lockable.
2049
return self._real_branch.get_physical_lock_status()
2051
def get_stacked_on_url(self):
2052
"""Get the URL this branch is stacked against.
2054
:raises NotStacked: If the branch is not stacked.
2055
:raises UnstackableBranchFormat: If the branch does not support
2057
:raises UnstackableRepositoryFormat: If the repository does not support
2061
# there may not be a repository yet, so we can't use
2062
# self._translate_error, so we can't use self._call either.
2063
response = self._client.call('Branch.get_stacked_on_url',
2064
self._remote_path())
2065
except errors.ErrorFromSmartServer, err:
2066
# there may not be a repository yet, so we can't call through
2067
# its _translate_error
2068
_translate_error(err, branch=self)
2069
except errors.UnknownSmartMethod, err:
2071
return self._real_branch.get_stacked_on_url()
2072
if response[0] != 'ok':
2073
raise errors.UnexpectedSmartServerResponse(response)
2076
def _vfs_get_tags_bytes(self):
2078
return self._real_branch._get_tags_bytes()
2080
def _get_tags_bytes(self):
2081
medium = self._client._medium
2082
if medium._is_remote_before((1, 13)):
2083
return self._vfs_get_tags_bytes()
2085
response = self._call('Branch.get_tags_bytes', self._remote_path())
2086
except errors.UnknownSmartMethod:
2087
medium._remember_remote_is_before((1, 13))
2088
return self._vfs_get_tags_bytes()
2091
def lock_read(self):
2092
self.repository.lock_read()
2093
if not self._lock_mode:
2094
self._lock_mode = 'r'
2095
self._lock_count = 1
2096
if self._real_branch is not None:
2097
self._real_branch.lock_read()
2099
self._lock_count += 1
2101
def _remote_lock_write(self, token):
2103
branch_token = repo_token = ''
2105
branch_token = token
2106
repo_token = self.repository.lock_write()
2107
self.repository.unlock()
2108
err_context = {'token': token}
2109
response = self._call(
2110
'Branch.lock_write', self._remote_path(), branch_token,
2111
repo_token or '', **err_context)
2112
if response[0] != 'ok':
2113
raise errors.UnexpectedSmartServerResponse(response)
2114
ok, branch_token, repo_token = response
2115
return branch_token, repo_token
2117
def lock_write(self, token=None):
2118
if not self._lock_mode:
2119
# Lock the branch and repo in one remote call.
2120
remote_tokens = self._remote_lock_write(token)
2121
self._lock_token, self._repo_lock_token = remote_tokens
2122
if not self._lock_token:
2123
raise SmartProtocolError('Remote server did not return a token!')
2124
# Tell the self.repository object that it is locked.
2125
self.repository.lock_write(
2126
self._repo_lock_token, _skip_rpc=True)
2128
if self._real_branch is not None:
2129
self._real_branch.lock_write(token=self._lock_token)
2130
if token is not None:
2131
self._leave_lock = True
2133
self._leave_lock = False
2134
self._lock_mode = 'w'
2135
self._lock_count = 1
2136
elif self._lock_mode == 'r':
2137
raise errors.ReadOnlyTransaction
2139
if token is not None:
2140
# A token was given to lock_write, and we're relocking, so
2141
# check that the given token actually matches the one we
2143
if token != self._lock_token:
2144
raise errors.TokenMismatch(token, self._lock_token)
2145
self._lock_count += 1
2146
# Re-lock the repository too.
2147
self.repository.lock_write(self._repo_lock_token)
2148
return self._lock_token or None
2150
def _set_tags_bytes(self, bytes):
2152
return self._real_branch._set_tags_bytes(bytes)
2154
def _unlock(self, branch_token, repo_token):
2155
err_context = {'token': str((branch_token, repo_token))}
2156
response = self._call(
2157
'Branch.unlock', self._remote_path(), branch_token,
2158
repo_token or '', **err_context)
2159
if response == ('ok',):
2161
raise errors.UnexpectedSmartServerResponse(response)
2165
self._lock_count -= 1
2166
if not self._lock_count:
2167
self._clear_cached_state()
2168
mode = self._lock_mode
2169
self._lock_mode = None
2170
if self._real_branch is not None:
2171
if (not self._leave_lock and mode == 'w' and
2172
self._repo_lock_token):
2173
# If this RemoteBranch will remove the physical lock
2174
# for the repository, make sure the _real_branch
2175
# doesn't do it first. (Because the _real_branch's
2176
# repository is set to be the RemoteRepository.)
2177
self._real_branch.repository.leave_lock_in_place()
2178
self._real_branch.unlock()
2180
# Only write-locked branched need to make a remote method
2181
# call to perform the unlock.
2183
if not self._lock_token:
2184
raise AssertionError('Locked, but no token!')
2185
branch_token = self._lock_token
2186
repo_token = self._repo_lock_token
2187
self._lock_token = None
2188
self._repo_lock_token = None
2189
if not self._leave_lock:
2190
self._unlock(branch_token, repo_token)
2192
self.repository.unlock()
2194
def break_lock(self):
2196
return self._real_branch.break_lock()
2198
def leave_lock_in_place(self):
2199
if not self._lock_token:
2200
raise NotImplementedError(self.leave_lock_in_place)
2201
self._leave_lock = True
2203
def dont_leave_lock_in_place(self):
2204
if not self._lock_token:
2205
raise NotImplementedError(self.dont_leave_lock_in_place)
2206
self._leave_lock = False
2208
def _last_revision_info(self):
2209
response = self._call('Branch.last_revision_info', self._remote_path())
2210
if response[0] != 'ok':
2211
raise SmartProtocolError('unexpected response code %s' % (response,))
2212
revno = int(response[1])
2213
last_revision = response[2]
2214
return (revno, last_revision)
2216
def _gen_revision_history(self):
2217
"""See Branch._gen_revision_history()."""
2218
response_tuple, response_handler = self._call_expecting_body(
2219
'Branch.revision_history', self._remote_path())
2220
if response_tuple[0] != 'ok':
2221
raise errors.UnexpectedSmartServerResponse(response_tuple)
2222
result = response_handler.read_body_bytes().split('\x00')
2227
def _remote_path(self):
2228
return self.bzrdir._path_for_remote_call(self._client)
2230
def _set_last_revision_descendant(self, revision_id, other_branch,
2231
allow_diverged=False, allow_overwrite_descendant=False):
2232
# This performs additional work to meet the hook contract; while its
2233
# undesirable, we have to synthesise the revno to call the hook, and
2234
# not calling the hook is worse as it means changes can't be prevented.
2235
# Having calculated this though, we can't just call into
2236
# set_last_revision_info as a simple call, because there is a set_rh
2237
# hook that some folk may still be using.
2238
old_revno, old_revid = self.last_revision_info()
2239
history = self._lefthand_history(revision_id)
2240
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2241
err_context = {'other_branch': other_branch}
2242
response = self._call('Branch.set_last_revision_ex',
2243
self._remote_path(), self._lock_token, self._repo_lock_token,
2244
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2246
self._clear_cached_state()
2247
if len(response) != 3 and response[0] != 'ok':
2248
raise errors.UnexpectedSmartServerResponse(response)
2249
new_revno, new_revision_id = response[1:]
2250
self._last_revision_info_cache = new_revno, new_revision_id
2251
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2252
if self._real_branch is not None:
2253
cache = new_revno, new_revision_id
2254
self._real_branch._last_revision_info_cache = cache
2256
def _set_last_revision(self, revision_id):
2257
old_revno, old_revid = self.last_revision_info()
2258
# This performs additional work to meet the hook contract; while its
2259
# undesirable, we have to synthesise the revno to call the hook, and
2260
# not calling the hook is worse as it means changes can't be prevented.
2261
# Having calculated this though, we can't just call into
2262
# set_last_revision_info as a simple call, because there is a set_rh
2263
# hook that some folk may still be using.
2264
history = self._lefthand_history(revision_id)
2265
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2266
self._clear_cached_state()
2267
response = self._call('Branch.set_last_revision',
2268
self._remote_path(), self._lock_token, self._repo_lock_token,
2270
if response != ('ok',):
2271
raise errors.UnexpectedSmartServerResponse(response)
2272
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2275
def set_revision_history(self, rev_history):
2276
# Send just the tip revision of the history; the server will generate
2277
# the full history from that. If the revision doesn't exist in this
2278
# branch, NoSuchRevision will be raised.
2279
if rev_history == []:
2282
rev_id = rev_history[-1]
2283
self._set_last_revision(rev_id)
2284
for hook in branch.Branch.hooks['set_rh']:
2285
hook(self, rev_history)
2286
self._cache_revision_history(rev_history)
2288
def _get_parent_location(self):
2289
medium = self._client._medium
2290
if medium._is_remote_before((1, 13)):
2291
return self._vfs_get_parent_location()
2293
response = self._call('Branch.get_parent', self._remote_path())
2294
except errors.UnknownSmartMethod:
2295
medium._remember_remote_is_before((1, 13))
2296
return self._vfs_get_parent_location()
2297
if len(response) != 1:
2298
raise errors.UnexpectedSmartServerResponse(response)
2299
parent_location = response[0]
2300
if parent_location == '':
2302
return parent_location
2304
def _vfs_get_parent_location(self):
2306
return self._real_branch._get_parent_location()
2308
def set_parent(self, url):
2310
return self._real_branch.set_parent(url)
2312
def _set_parent_location(self, url):
2313
# Used by tests, to poke bad urls into branch configurations
2315
self.set_parent(url)
2318
return self._real_branch._set_parent_location(url)
2321
def pull(self, source, overwrite=False, stop_revision=None,
2323
self._clear_cached_state_of_remote_branch_only()
2325
return self._real_branch.pull(
2326
source, overwrite=overwrite, stop_revision=stop_revision,
2327
_override_hook_target=self, **kwargs)
2330
def push(self, target, overwrite=False, stop_revision=None):
2332
return self._real_branch.push(
2333
target, overwrite=overwrite, stop_revision=stop_revision,
2334
_override_hook_source_branch=self)
2336
def is_locked(self):
2337
return self._lock_count >= 1
2340
def revision_id_to_revno(self, revision_id):
2342
return self._real_branch.revision_id_to_revno(revision_id)
2345
def set_last_revision_info(self, revno, revision_id):
2346
# XXX: These should be returned by the set_last_revision_info verb
2347
old_revno, old_revid = self.last_revision_info()
2348
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2349
revision_id = ensure_null(revision_id)
2351
response = self._call('Branch.set_last_revision_info',
2352
self._remote_path(), self._lock_token, self._repo_lock_token,
2353
str(revno), revision_id)
2354
except errors.UnknownSmartMethod:
2356
self._clear_cached_state_of_remote_branch_only()
2357
self._real_branch.set_last_revision_info(revno, revision_id)
2358
self._last_revision_info_cache = revno, revision_id
2360
if response == ('ok',):
2361
self._clear_cached_state()
2362
self._last_revision_info_cache = revno, revision_id
2363
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2364
# Update the _real_branch's cache too.
2365
if self._real_branch is not None:
2366
cache = self._last_revision_info_cache
2367
self._real_branch._last_revision_info_cache = cache
2369
raise errors.UnexpectedSmartServerResponse(response)
2372
def generate_revision_history(self, revision_id, last_rev=None,
2374
medium = self._client._medium
2375
if not medium._is_remote_before((1, 6)):
2376
# Use a smart method for 1.6 and above servers
2378
self._set_last_revision_descendant(revision_id, other_branch,
2379
allow_diverged=True, allow_overwrite_descendant=True)
2381
except errors.UnknownSmartMethod:
2382
medium._remember_remote_is_before((1, 6))
2383
self._clear_cached_state_of_remote_branch_only()
2384
self.set_revision_history(self._lefthand_history(revision_id,
2385
last_rev=last_rev,other_branch=other_branch))
2387
def set_push_location(self, location):
2389
return self._real_branch.set_push_location(location)
2392
class RemoteBranchConfig(object):
2393
"""A Config that reads from a smart branch and writes via smart methods.
2395
It is a low-level object that considers config data to be name/value pairs
2396
that may be associated with a section. Assigning meaning to the these
2397
values is done at higher levels like bzrlib.config.TreeConfig.
2400
def __init__(self, branch):
2401
self._branch = branch
2403
def get_option(self, name, section=None, default=None):
2404
"""Return the value associated with a named option.
2406
:param name: The name of the value
2407
:param section: The section the option is in (if any)
2408
:param default: The value to return if the value is not set
2409
:return: The value or default value
2411
configobj = self._get_configobj()
2413
section_obj = configobj
2416
section_obj = configobj[section]
2419
return section_obj.get(name, default)
2421
def _get_configobj(self):
2422
path = self._branch._remote_path()
2423
response = self._branch._client.call_expecting_body(
2424
'Branch.get_config_file', path)
2425
if response[0][0] != 'ok':
2426
raise UnexpectedSmartServerResponse(response)
2427
lines = response[1].read_body_bytes().splitlines()
2428
return config.ConfigObj(lines, encoding='utf-8')
2430
def set_option(self, value, name, section=None):
2431
"""Set the value associated with a named option.
2433
:param value: The value to set
2434
:param name: The name of the value to set
2435
:param section: The section the option is in (if any)
2437
medium = self._branch._client._medium
2438
if medium._is_remote_before((1, 14)):
2439
return self._vfs_set_option(value, name, section)
2441
path = self._branch._remote_path()
2442
response = self._branch._client.call('Branch.set_config_option',
2443
path, self._branch._lock_token, self._branch._repo_lock_token,
2444
value.encode('utf8'), name, section or '')
2445
except errors.UnknownSmartMethod:
2446
medium._remember_remote_is_before((1, 14))
2447
return self._vfs_set_option(value, name, section)
2449
raise errors.UnexpectedSmartServerResponse(response)
2451
def _vfs_set_option(self, value, name, section=None):
2452
self._branch._ensure_real()
2453
return self._branch._real_branch._get_config().set_option(
2454
value, name, section)
2457
def _extract_tar(tar, to_dir):
2458
"""Extract all the contents of a tarfile object.
2460
A replacement for extractall, which is not present in python2.4
2463
tar.extract(tarinfo, to_dir)
2466
def _translate_error(err, **context):
2467
"""Translate an ErrorFromSmartServer into a more useful error.
2469
Possible context keys:
2477
If the error from the server doesn't match a known pattern, then
2478
UnknownErrorFromSmartServer is raised.
2482
return context[name]
2483
except KeyError, key_err:
2484
mutter('Missing key %r in context %r', key_err.args[0], context)
2487
"""Get the path from the context if present, otherwise use first error
2491
return context['path']
2492
except KeyError, key_err:
2494
return err.error_args[0]
2495
except IndexError, idx_err:
2497
'Missing key %r in context %r', key_err.args[0], context)
2500
if err.error_verb == 'NoSuchRevision':
2501
raise NoSuchRevision(find('branch'), err.error_args[0])
2502
elif err.error_verb == 'nosuchrevision':
2503
raise NoSuchRevision(find('repository'), err.error_args[0])
2504
elif err.error_tuple == ('nobranch',):
2505
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2506
elif err.error_verb == 'norepository':
2507
raise errors.NoRepositoryPresent(find('bzrdir'))
2508
elif err.error_verb == 'LockContention':
2509
raise errors.LockContention('(remote lock)')
2510
elif err.error_verb == 'UnlockableTransport':
2511
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2512
elif err.error_verb == 'LockFailed':
2513
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2514
elif err.error_verb == 'TokenMismatch':
2515
raise errors.TokenMismatch(find('token'), '(remote token)')
2516
elif err.error_verb == 'Diverged':
2517
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2518
elif err.error_verb == 'TipChangeRejected':
2519
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2520
elif err.error_verb == 'UnstackableBranchFormat':
2521
raise errors.UnstackableBranchFormat(*err.error_args)
2522
elif err.error_verb == 'UnstackableRepositoryFormat':
2523
raise errors.UnstackableRepositoryFormat(*err.error_args)
2524
elif err.error_verb == 'NotStacked':
2525
raise errors.NotStacked(branch=find('branch'))
2526
elif err.error_verb == 'PermissionDenied':
2528
if len(err.error_args) >= 2:
2529
extra = err.error_args[1]
2532
raise errors.PermissionDenied(path, extra=extra)
2533
elif err.error_verb == 'ReadError':
2535
raise errors.ReadError(path)
2536
elif err.error_verb == 'NoSuchFile':
2538
raise errors.NoSuchFile(path)
2539
elif err.error_verb == 'FileExists':
2540
raise errors.FileExists(err.error_args[0])
2541
elif err.error_verb == 'DirectoryNotEmpty':
2542
raise errors.DirectoryNotEmpty(err.error_args[0])
2543
elif err.error_verb == 'ShortReadvError':
2544
args = err.error_args
2545
raise errors.ShortReadvError(
2546
args[0], int(args[1]), int(args[2]), int(args[3]))
2547
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2548
encoding = str(err.error_args[0]) # encoding must always be a string
2549
val = err.error_args[1]
2550
start = int(err.error_args[2])
2551
end = int(err.error_args[3])
2552
reason = str(err.error_args[4]) # reason must always be a string
2553
if val.startswith('u:'):
2554
val = val[2:].decode('utf-8')
2555
elif val.startswith('s:'):
2556
val = val[2:].decode('base64')
2557
if err.error_verb == 'UnicodeDecodeError':
2558
raise UnicodeDecodeError(encoding, val, start, end, reason)
2559
elif err.error_verb == 'UnicodeEncodeError':
2560
raise UnicodeEncodeError(encoding, val, start, end, reason)
2561
elif err.error_verb == 'ReadOnlyError':
2562
raise errors.TransportNotPossible('readonly transport')
2563
raise errors.UnknownErrorFromSmartServer(err)