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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
35
from bzrlib.branch import BranchReferenceFormat
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.smart import client, vfs, repository as smart_repo
44
from bzrlib.revision import ensure_null, NULL_REVISION
45
from bzrlib.trace import mutter, note, warning
46
from bzrlib.util import bencode
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
73
def response_tuple_to_repo_format(response):
74
"""Convert a response tuple describing a repository format to a format."""
75
format = RemoteRepositoryFormat()
76
format.rich_root_data = (response[0] == 'yes')
77
format.supports_tree_reference = (response[1] == 'yes')
78
format.supports_external_lookups = (response[2] == 'yes')
79
format._network_name = response[3]
83
# Note: RemoteBzrDirFormat is in bzrdir.py
85
class RemoteBzrDir(BzrDir, _RpcHelper):
86
"""Control directory on a remote server, accessed via bzr:// or similar."""
88
def __init__(self, transport, format, _client=None):
89
"""Construct a RemoteBzrDir.
91
:param _client: Private parameter for testing. Disables probing and the
94
BzrDir.__init__(self, transport, format)
95
# this object holds a delegated bzrdir that uses file-level operations
96
# to talk to the other side
97
self._real_bzrdir = None
98
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
# create_branch for details.
100
self._next_open_branch_result = None
103
medium = transport.get_smart_medium()
104
self._client = client._SmartClient(medium)
106
self._client = _client
109
path = self._path_for_remote_call(self._client)
110
response = self._call('BzrDir.open', path)
111
if response not in [('yes',), ('no',)]:
112
raise errors.UnexpectedSmartServerResponse(response)
113
if response == ('no',):
114
raise errors.NotBranchError(path=transport.base)
116
def _ensure_real(self):
117
"""Ensure that there is a _real_bzrdir set.
119
Used before calls to self._real_bzrdir.
121
if not self._real_bzrdir:
122
self._real_bzrdir = BzrDir.open_from_transport(
123
self.root_transport, _server_formats=False)
125
def _translate_error(self, err, **context):
126
_translate_error(err, bzrdir=self, **context)
128
def break_lock(self):
129
# Prevent aliasing problems in the next_open_branch_result cache.
130
# See create_branch for rationale.
131
self._next_open_branch_result = None
132
return BzrDir.break_lock(self)
134
def cloning_metadir(self, stacked=False):
136
return self._real_bzrdir.cloning_metadir(stacked)
138
def create_repository(self, shared=False):
139
# as per meta1 formats - just delegate to the format object which may
141
result = self._format.repository_format.initialize(self, shared)
142
if not isinstance(result, RemoteRepository):
143
return self.open_repository()
147
def destroy_repository(self):
148
"""See BzrDir.destroy_repository"""
150
self._real_bzrdir.destroy_repository()
152
def create_branch(self):
153
# as per meta1 formats - just delegate to the format object which may
155
real_branch = self._format.get_branch_format().initialize(self)
156
if not isinstance(real_branch, RemoteBranch):
157
result = RemoteBranch(self, self.find_repository(), real_branch)
160
# BzrDir.clone_on_transport() uses the result of create_branch but does
161
# not return it to its callers; we save approximately 8% of our round
162
# trips by handing the branch we created back to the first caller to
163
# open_branch rather than probing anew. Long term we need a API in
164
# bzrdir that doesn't discard result objects (like result_branch).
166
self._next_open_branch_result = result
169
def destroy_branch(self):
170
"""See BzrDir.destroy_branch"""
172
self._real_bzrdir.destroy_branch()
173
self._next_open_branch_result = None
175
def create_workingtree(self, revision_id=None, from_branch=None):
176
raise errors.NotLocalUrl(self.transport.base)
178
def find_branch_format(self):
179
"""Find the branch 'format' for this bzrdir.
181
This might be a synthetic object for e.g. RemoteBranch and SVN.
183
b = self.open_branch()
186
def get_branch_reference(self):
187
"""See BzrDir.get_branch_reference()."""
188
path = self._path_for_remote_call(self._client)
189
response = self._call('BzrDir.open_branch', path)
190
if response[0] == 'ok':
191
if response[1] == '':
192
# branch at this location.
195
# a branch reference, use the existing BranchReference logic.
198
raise errors.UnexpectedSmartServerResponse(response)
200
def _get_tree_branch(self):
201
"""See BzrDir._get_tree_branch()."""
202
return None, self.open_branch()
204
def open_branch(self, _unsupported=False):
206
raise NotImplementedError('unsupported flag support not implemented yet.')
207
if self._next_open_branch_result is not None:
208
# See create_branch for details.
209
result = self._next_open_branch_result
210
self._next_open_branch_result = None
212
reference_url = self.get_branch_reference()
213
if reference_url is None:
214
# branch at this location.
215
return RemoteBranch(self, self.find_repository())
217
# a branch reference, use the existing BranchReference logic.
218
format = BranchReferenceFormat()
219
return format.open(self, _found=True, location=reference_url)
221
def _open_repo_v1(self, path):
222
verb = 'BzrDir.find_repository'
223
response = self._call(verb, path)
224
if response[0] != 'ok':
225
raise errors.UnexpectedSmartServerResponse(response)
226
# servers that only support the v1 method don't support external
229
repo = self._real_bzrdir.open_repository()
230
response = response + ('no', repo._format.network_name())
231
return response, repo
233
def _open_repo_v2(self, path):
234
verb = 'BzrDir.find_repositoryV2'
235
response = self._call(verb, path)
236
if response[0] != 'ok':
237
raise errors.UnexpectedSmartServerResponse(response)
239
repo = self._real_bzrdir.open_repository()
240
response = response + (repo._format.network_name(),)
241
return response, repo
243
def _open_repo_v3(self, path):
244
verb = 'BzrDir.find_repositoryV3'
245
medium = self._client._medium
246
if medium._is_remote_before((1, 13)):
247
raise errors.UnknownSmartMethod(verb)
248
response = self._call(verb, path)
249
if response[0] != 'ok':
250
raise errors.UnexpectedSmartServerResponse(response)
251
return response, None
253
def open_repository(self):
254
path = self._path_for_remote_call(self._client)
256
for probe in [self._open_repo_v3, self._open_repo_v2,
259
response, real_repo = probe(path)
261
except errors.UnknownSmartMethod:
264
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
265
if response[0] != 'ok':
266
raise errors.UnexpectedSmartServerResponse(response)
267
if len(response) != 6:
268
raise SmartProtocolError('incorrect response length %s' % (response,))
269
if response[1] == '':
270
# repo is at this dir.
271
format = response_tuple_to_repo_format(response[2:])
272
# Used to support creating a real format instance when needed.
273
format._creating_bzrdir = self
274
remote_repo = RemoteRepository(self, format)
275
format._creating_repo = remote_repo
276
if real_repo is not None:
277
remote_repo._set_real_repository(real_repo)
280
raise errors.NoRepositoryPresent(self)
282
def open_workingtree(self, recommend_upgrade=True):
284
if self._real_bzrdir.has_workingtree():
285
raise errors.NotLocalUrl(self.root_transport)
287
raise errors.NoWorkingTree(self.root_transport.base)
289
def _path_for_remote_call(self, client):
290
"""Return the path to be used for this bzrdir in a remote call."""
291
return client.remote_path_from_transport(self.root_transport)
293
def get_branch_transport(self, branch_format):
295
return self._real_bzrdir.get_branch_transport(branch_format)
297
def get_repository_transport(self, repository_format):
299
return self._real_bzrdir.get_repository_transport(repository_format)
301
def get_workingtree_transport(self, workingtree_format):
303
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
305
def can_convert_format(self):
306
"""Upgrading of remote bzrdirs is not supported yet."""
309
def needs_format_conversion(self, format=None):
310
"""Upgrading of remote bzrdirs is not supported yet."""
312
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
313
% 'needs_format_conversion(format=None)')
316
def clone(self, url, revision_id=None, force_new_repo=False,
317
preserve_stacking=False):
319
return self._real_bzrdir.clone(url, revision_id=revision_id,
320
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
322
def get_config(self):
324
return self._real_bzrdir.get_config()
327
class RemoteRepositoryFormat(repository.RepositoryFormat):
328
"""Format for repositories accessed over a _SmartClient.
330
Instances of this repository are represented by RemoteRepository
333
The RemoteRepositoryFormat is parameterized during construction
334
to reflect the capabilities of the real, remote format. Specifically
335
the attributes rich_root_data and supports_tree_reference are set
336
on a per instance basis, and are not set (and should not be) at
339
:ivar _custom_format: If set, a specific concrete repository format that
340
will be used when initializing a repository with this
341
RemoteRepositoryFormat.
342
:ivar _creating_repo: If set, the repository object that this
343
RemoteRepositoryFormat was created for: it can be called into
344
to obtain data like the network name.
347
_matchingbzrdir = RemoteBzrDirFormat()
350
repository.RepositoryFormat.__init__(self)
351
self._custom_format = None
352
self._network_name = None
353
self._creating_bzrdir = None
355
def _vfs_initialize(self, a_bzrdir, shared):
356
"""Helper for common code in initialize."""
357
if self._custom_format:
358
# Custom format requested
359
result = self._custom_format.initialize(a_bzrdir, shared=shared)
360
elif self._creating_bzrdir is not None:
361
# Use the format that the repository we were created to back
363
prior_repo = self._creating_bzrdir.open_repository()
364
prior_repo._ensure_real()
365
result = prior_repo._real_repository._format.initialize(
366
a_bzrdir, shared=shared)
368
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
369
# support remote initialization.
370
# We delegate to a real object at this point (as RemoteBzrDir
371
# delegate to the repository format which would lead to infinite
372
# recursion if we just called a_bzrdir.create_repository.
373
a_bzrdir._ensure_real()
374
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
375
if not isinstance(result, RemoteRepository):
376
return self.open(a_bzrdir)
380
def initialize(self, a_bzrdir, shared=False):
381
# Being asked to create on a non RemoteBzrDir:
382
if not isinstance(a_bzrdir, RemoteBzrDir):
383
return self._vfs_initialize(a_bzrdir, shared)
384
medium = a_bzrdir._client._medium
385
if medium._is_remote_before((1, 13)):
386
return self._vfs_initialize(a_bzrdir, shared)
387
# Creating on a remote bzr dir.
388
# 1) get the network name to use.
389
if self._custom_format:
390
network_name = self._custom_format.network_name()
392
# Select the current bzrlib default and ask for that.
393
reference_bzrdir_format = bzrdir.format_registry.get('default')()
394
reference_format = reference_bzrdir_format.repository_format
395
network_name = reference_format.network_name()
396
# 2) try direct creation via RPC
397
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
398
verb = 'BzrDir.create_repository'
404
response = a_bzrdir._call(verb, path, network_name, shared_str)
405
except errors.UnknownSmartMethod:
406
# Fallback - use vfs methods
407
return self._vfs_initialize(a_bzrdir, shared)
409
# Turn the response into a RemoteRepository object.
410
format = response_tuple_to_repo_format(response[1:])
411
# Used to support creating a real format instance when needed.
412
format._creating_bzrdir = a_bzrdir
413
remote_repo = RemoteRepository(a_bzrdir, format)
414
format._creating_repo = remote_repo
417
def open(self, a_bzrdir):
418
if not isinstance(a_bzrdir, RemoteBzrDir):
419
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
420
return a_bzrdir.open_repository()
422
def _ensure_real(self):
423
if self._custom_format is None:
424
self._custom_format = repository.network_format_registry.get(
428
def _fetch_order(self):
430
return self._custom_format._fetch_order
433
def _fetch_uses_deltas(self):
435
return self._custom_format._fetch_uses_deltas
438
def _fetch_reconcile(self):
440
return self._custom_format._fetch_reconcile
442
def get_format_description(self):
443
return 'bzr remote repository'
445
def __eq__(self, other):
446
return self.__class__ == other.__class__
448
def check_conversion_target(self, target_format):
449
if self.rich_root_data and not target_format.rich_root_data:
450
raise errors.BadConversionTarget(
451
'Does not support rich root data.', target_format)
452
if (self.supports_tree_reference and
453
not getattr(target_format, 'supports_tree_reference', False)):
454
raise errors.BadConversionTarget(
455
'Does not support nested trees', target_format)
457
def network_name(self):
458
if self._network_name:
459
return self._network_name
460
self._creating_repo._ensure_real()
461
return self._creating_repo._real_repository._format.network_name()
464
def _serializer(self):
466
return self._custom_format._serializer
469
class RemoteRepository(_RpcHelper):
470
"""Repository accessed over rpc.
472
For the moment most operations are performed using local transport-backed
476
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
477
"""Create a RemoteRepository instance.
479
:param remote_bzrdir: The bzrdir hosting this repository.
480
:param format: The RemoteFormat object to use.
481
:param real_repository: If not None, a local implementation of the
482
repository logic for the repository, usually accessing the data
484
:param _client: Private testing parameter - override the smart client
485
to be used by the repository.
488
self._real_repository = real_repository
490
self._real_repository = None
491
self.bzrdir = remote_bzrdir
493
self._client = remote_bzrdir._client
495
self._client = _client
496
self._format = format
497
self._lock_mode = None
498
self._lock_token = None
500
self._leave_lock = False
501
self._unstacked_provider = graph.CachingParentsProvider(
502
get_parent_map=self._get_parent_map_rpc)
503
self._unstacked_provider.disable_cache()
505
# These depend on the actual remote format, so force them off for
506
# maximum compatibility. XXX: In future these should depend on the
507
# remote repository instance, but this is irrelevant until we perform
508
# reconcile via an RPC call.
509
self._reconcile_does_inventory_gc = False
510
self._reconcile_fixes_text_parents = False
511
self._reconcile_backsup_inventory = False
512
self.base = self.bzrdir.transport.base
513
# Additional places to query for data.
514
self._fallback_repositories = []
517
return "%s(%s)" % (self.__class__.__name__, self.base)
521
def abort_write_group(self, suppress_errors=False):
522
"""Complete a write group on the decorated repository.
524
Smart methods peform operations in a single step so this api
525
is not really applicable except as a compatibility thunk
526
for older plugins that don't use e.g. the CommitBuilder
529
:param suppress_errors: see Repository.abort_write_group.
532
return self._real_repository.abort_write_group(
533
suppress_errors=suppress_errors)
535
def commit_write_group(self):
536
"""Complete a write group on the decorated repository.
538
Smart methods peform operations in a single step so this api
539
is not really applicable except as a compatibility thunk
540
for older plugins that don't use e.g. the CommitBuilder
544
return self._real_repository.commit_write_group()
546
def resume_write_group(self, tokens):
548
return self._real_repository.resume_write_group(tokens)
550
def suspend_write_group(self):
552
return self._real_repository.suspend_write_group()
554
def _ensure_real(self):
555
"""Ensure that there is a _real_repository set.
557
Used before calls to self._real_repository.
559
if self._real_repository is None:
560
self.bzrdir._ensure_real()
561
self._set_real_repository(
562
self.bzrdir._real_bzrdir.open_repository())
564
def _translate_error(self, err, **context):
565
self.bzrdir._translate_error(err, repository=self, **context)
567
def find_text_key_references(self):
568
"""Find the text key references within the repository.
570
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
571
revision_ids. Each altered file-ids has the exact revision_ids that
572
altered it listed explicitly.
573
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
574
to whether they were referred to by the inventory of the
575
revision_id that they contain. The inventory texts from all present
576
revision ids are assessed to generate this report.
579
return self._real_repository.find_text_key_references()
581
def _generate_text_key_index(self):
582
"""Generate a new text key index for the repository.
584
This is an expensive function that will take considerable time to run.
586
:return: A dict mapping (file_id, revision_id) tuples to a list of
587
parents, also (file_id, revision_id) tuples.
590
return self._real_repository._generate_text_key_index()
592
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
593
def get_revision_graph(self, revision_id=None):
594
"""See Repository.get_revision_graph()."""
595
return self._get_revision_graph(revision_id)
597
def _get_revision_graph(self, revision_id):
598
"""Private method for using with old (< 1.2) servers to fallback."""
599
if revision_id is None:
601
elif revision.is_null(revision_id):
604
path = self.bzrdir._path_for_remote_call(self._client)
605
response = self._call_expecting_body(
606
'Repository.get_revision_graph', path, revision_id)
607
response_tuple, response_handler = response
608
if response_tuple[0] != 'ok':
609
raise errors.UnexpectedSmartServerResponse(response_tuple)
610
coded = response_handler.read_body_bytes()
612
# no revisions in this repository!
614
lines = coded.split('\n')
617
d = tuple(line.split())
618
revision_graph[d[0]] = d[1:]
620
return revision_graph
623
"""See Repository._get_sink()."""
624
return RemoteStreamSink(self)
626
def _get_source(self, to_format):
627
"""Return a source for streaming from this repository."""
628
return RemoteStreamSource(self, to_format)
630
def has_revision(self, revision_id):
631
"""See Repository.has_revision()."""
632
if revision_id == NULL_REVISION:
633
# The null revision is always present.
635
path = self.bzrdir._path_for_remote_call(self._client)
636
response = self._call('Repository.has_revision', path, revision_id)
637
if response[0] not in ('yes', 'no'):
638
raise errors.UnexpectedSmartServerResponse(response)
639
if response[0] == 'yes':
641
for fallback_repo in self._fallback_repositories:
642
if fallback_repo.has_revision(revision_id):
646
def has_revisions(self, revision_ids):
647
"""See Repository.has_revisions()."""
648
# FIXME: This does many roundtrips, particularly when there are
649
# fallback repositories. -- mbp 20080905
651
for revision_id in revision_ids:
652
if self.has_revision(revision_id):
653
result.add(revision_id)
656
def has_same_location(self, other):
657
return (self.__class__ == other.__class__ and
658
self.bzrdir.transport.base == other.bzrdir.transport.base)
660
def get_graph(self, other_repository=None):
661
"""Return the graph for this repository format"""
662
parents_provider = self._make_parents_provider(other_repository)
663
return graph.Graph(parents_provider)
665
def gather_stats(self, revid=None, committers=None):
666
"""See Repository.gather_stats()."""
667
path = self.bzrdir._path_for_remote_call(self._client)
668
# revid can be None to indicate no revisions, not just NULL_REVISION
669
if revid is None or revision.is_null(revid):
673
if committers is None or not committers:
674
fmt_committers = 'no'
676
fmt_committers = 'yes'
677
response_tuple, response_handler = self._call_expecting_body(
678
'Repository.gather_stats', path, fmt_revid, fmt_committers)
679
if response_tuple[0] != 'ok':
680
raise errors.UnexpectedSmartServerResponse(response_tuple)
682
body = response_handler.read_body_bytes()
684
for line in body.split('\n'):
687
key, val_text = line.split(':')
688
if key in ('revisions', 'size', 'committers'):
689
result[key] = int(val_text)
690
elif key in ('firstrev', 'latestrev'):
691
values = val_text.split(' ')[1:]
692
result[key] = (float(values[0]), long(values[1]))
696
def find_branches(self, using=False):
697
"""See Repository.find_branches()."""
698
# should be an API call to the server.
700
return self._real_repository.find_branches(using=using)
702
def get_physical_lock_status(self):
703
"""See Repository.get_physical_lock_status()."""
704
# should be an API call to the server.
706
return self._real_repository.get_physical_lock_status()
708
def is_in_write_group(self):
709
"""Return True if there is an open write group.
711
write groups are only applicable locally for the smart server..
713
if self._real_repository:
714
return self._real_repository.is_in_write_group()
717
return self._lock_count >= 1
720
"""See Repository.is_shared()."""
721
path = self.bzrdir._path_for_remote_call(self._client)
722
response = self._call('Repository.is_shared', path)
723
if response[0] not in ('yes', 'no'):
724
raise SmartProtocolError('unexpected response code %s' % (response,))
725
return response[0] == 'yes'
727
def is_write_locked(self):
728
return self._lock_mode == 'w'
731
# wrong eventually - want a local lock cache context
732
if not self._lock_mode:
733
self._lock_mode = 'r'
735
self._unstacked_provider.enable_cache(cache_misses=False)
736
if self._real_repository is not None:
737
self._real_repository.lock_read()
739
self._lock_count += 1
741
def _remote_lock_write(self, token):
742
path = self.bzrdir._path_for_remote_call(self._client)
745
err_context = {'token': token}
746
response = self._call('Repository.lock_write', path, token,
748
if response[0] == 'ok':
752
raise errors.UnexpectedSmartServerResponse(response)
754
def lock_write(self, token=None, _skip_rpc=False):
755
if not self._lock_mode:
757
if self._lock_token is not None:
758
if token != self._lock_token:
759
raise errors.TokenMismatch(token, self._lock_token)
760
self._lock_token = token
762
self._lock_token = self._remote_lock_write(token)
763
# if self._lock_token is None, then this is something like packs or
764
# svn where we don't get to lock the repo, or a weave style repository
765
# where we cannot lock it over the wire and attempts to do so will
767
if self._real_repository is not None:
768
self._real_repository.lock_write(token=self._lock_token)
769
if token is not None:
770
self._leave_lock = True
772
self._leave_lock = False
773
self._lock_mode = 'w'
775
self._unstacked_provider.enable_cache(cache_misses=False)
776
elif self._lock_mode == 'r':
777
raise errors.ReadOnlyError(self)
779
self._lock_count += 1
780
return self._lock_token or None
782
def leave_lock_in_place(self):
783
if not self._lock_token:
784
raise NotImplementedError(self.leave_lock_in_place)
785
self._leave_lock = True
787
def dont_leave_lock_in_place(self):
788
if not self._lock_token:
789
raise NotImplementedError(self.dont_leave_lock_in_place)
790
self._leave_lock = False
792
def _set_real_repository(self, repository):
793
"""Set the _real_repository for this repository.
795
:param repository: The repository to fallback to for non-hpss
796
implemented operations.
798
if self._real_repository is not None:
799
# Replacing an already set real repository.
800
# We cannot do this [currently] if the repository is locked -
801
# synchronised state might be lost.
803
raise AssertionError('_real_repository is already set')
804
if isinstance(repository, RemoteRepository):
805
raise AssertionError()
806
self._real_repository = repository
807
for fb in self._fallback_repositories:
808
self._real_repository.add_fallback_repository(fb)
809
if self._lock_mode == 'w':
810
# if we are already locked, the real repository must be able to
811
# acquire the lock with our token.
812
self._real_repository.lock_write(self._lock_token)
813
elif self._lock_mode == 'r':
814
self._real_repository.lock_read()
816
def start_write_group(self):
817
"""Start a write group on the decorated repository.
819
Smart methods peform operations in a single step so this api
820
is not really applicable except as a compatibility thunk
821
for older plugins that don't use e.g. the CommitBuilder
825
return self._real_repository.start_write_group()
827
def _unlock(self, token):
828
path = self.bzrdir._path_for_remote_call(self._client)
830
# with no token the remote repository is not persistently locked.
832
err_context = {'token': token}
833
response = self._call('Repository.unlock', path, token,
835
if response == ('ok',):
838
raise errors.UnexpectedSmartServerResponse(response)
841
if not self._lock_count:
842
raise errors.LockNotHeld(self)
843
self._lock_count -= 1
844
if self._lock_count > 0:
846
self._unstacked_provider.disable_cache()
847
old_mode = self._lock_mode
848
self._lock_mode = None
850
# The real repository is responsible at present for raising an
851
# exception if it's in an unfinished write group. However, it
852
# normally will *not* actually remove the lock from disk - that's
853
# done by the server on receiving the Repository.unlock call.
854
# This is just to let the _real_repository stay up to date.
855
if self._real_repository is not None:
856
self._real_repository.unlock()
858
# The rpc-level lock should be released even if there was a
859
# problem releasing the vfs-based lock.
861
# Only write-locked repositories need to make a remote method
862
# call to perfom the unlock.
863
old_token = self._lock_token
864
self._lock_token = None
865
if not self._leave_lock:
866
self._unlock(old_token)
868
def break_lock(self):
869
# should hand off to the network
871
return self._real_repository.break_lock()
873
def _get_tarball(self, compression):
874
"""Return a TemporaryFile containing a repository tarball.
876
Returns None if the server does not support sending tarballs.
879
path = self.bzrdir._path_for_remote_call(self._client)
881
response, protocol = self._call_expecting_body(
882
'Repository.tarball', path, compression)
883
except errors.UnknownSmartMethod:
884
protocol.cancel_read_body()
886
if response[0] == 'ok':
887
# Extract the tarball and return it
888
t = tempfile.NamedTemporaryFile()
889
# TODO: rpc layer should read directly into it...
890
t.write(protocol.read_body_bytes())
893
raise errors.UnexpectedSmartServerResponse(response)
895
def sprout(self, to_bzrdir, revision_id=None):
896
# TODO: Option to control what format is created?
898
dest_repo = self._real_repository._format.initialize(to_bzrdir,
900
dest_repo.fetch(self, revision_id=revision_id)
903
### These methods are just thin shims to the VFS object for now.
905
def revision_tree(self, revision_id):
907
return self._real_repository.revision_tree(revision_id)
909
def get_serializer_format(self):
911
return self._real_repository.get_serializer_format()
913
def get_commit_builder(self, branch, parents, config, timestamp=None,
914
timezone=None, committer=None, revprops=None,
916
# FIXME: It ought to be possible to call this without immediately
917
# triggering _ensure_real. For now it's the easiest thing to do.
919
real_repo = self._real_repository
920
builder = real_repo.get_commit_builder(branch, parents,
921
config, timestamp=timestamp, timezone=timezone,
922
committer=committer, revprops=revprops, revision_id=revision_id)
925
def add_fallback_repository(self, repository):
926
"""Add a repository to use for looking up data not held locally.
928
:param repository: A repository.
930
# XXX: At the moment the RemoteRepository will allow fallbacks
931
# unconditionally - however, a _real_repository will usually exist,
932
# and may raise an error if it's not accommodated by the underlying
933
# format. Eventually we should check when opening the repository
934
# whether it's willing to allow them or not.
936
# We need to accumulate additional repositories here, to pass them in
939
self._fallback_repositories.append(repository)
940
# If self._real_repository was parameterised already (e.g. because a
941
# _real_branch had its get_stacked_on_url method called), then the
942
# repository to be added may already be in the _real_repositories list.
943
if self._real_repository is not None:
944
if repository not in self._real_repository._fallback_repositories:
945
self._real_repository.add_fallback_repository(repository)
947
# They are also seen by the fallback repository. If it doesn't
948
# exist yet they'll be added then. This implicitly copies them.
951
def add_inventory(self, revid, inv, parents):
953
return self._real_repository.add_inventory(revid, inv, parents)
955
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
958
return self._real_repository.add_inventory_by_delta(basis_revision_id,
959
delta, new_revision_id, parents)
961
def add_revision(self, rev_id, rev, inv=None, config=None):
963
return self._real_repository.add_revision(
964
rev_id, rev, inv=inv, config=config)
967
def get_inventory(self, revision_id):
969
return self._real_repository.get_inventory(revision_id)
971
def iter_inventories(self, revision_ids):
973
return self._real_repository.iter_inventories(revision_ids)
976
def get_revision(self, revision_id):
978
return self._real_repository.get_revision(revision_id)
980
def get_transaction(self):
982
return self._real_repository.get_transaction()
985
def clone(self, a_bzrdir, revision_id=None):
987
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
989
def make_working_trees(self):
990
"""See Repository.make_working_trees"""
992
return self._real_repository.make_working_trees()
994
def revision_ids_to_search_result(self, result_set):
995
"""Convert a set of revision ids to a graph SearchResult."""
996
result_parents = set()
997
for parents in self.get_graph().get_parent_map(
998
result_set).itervalues():
999
result_parents.update(parents)
1000
included_keys = result_set.intersection(result_parents)
1001
start_keys = result_set.difference(included_keys)
1002
exclude_keys = result_parents.difference(result_set)
1003
result = graph.SearchResult(start_keys, exclude_keys,
1004
len(result_set), result_set)
1008
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1009
"""Return the revision ids that other has that this does not.
1011
These are returned in topological order.
1013
revision_id: only return revision ids included by revision_id.
1015
return repository.InterRepository.get(
1016
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1018
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
1019
# Not delegated to _real_repository so that InterRepository.get has a
1020
# chance to find an InterRepository specialised for RemoteRepository.
1021
if self.has_same_location(source):
1022
# check that last_revision is in 'from' and then return a
1024
if (revision_id is not None and
1025
not revision.is_null(revision_id)):
1026
self.get_revision(revision_id)
1028
inter = repository.InterRepository.get(source, self)
1030
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
1031
except NotImplementedError:
1032
raise errors.IncompatibleRepositories(source, self)
1034
def create_bundle(self, target, base, fileobj, format=None):
1036
self._real_repository.create_bundle(target, base, fileobj, format)
1039
def get_ancestry(self, revision_id, topo_sorted=True):
1041
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1043
def fileids_altered_by_revision_ids(self, revision_ids):
1045
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1047
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1049
return self._real_repository._get_versioned_file_checker(
1050
revisions, revision_versions_cache)
1052
def iter_files_bytes(self, desired_files):
1053
"""See Repository.iter_file_bytes.
1056
return self._real_repository.iter_files_bytes(desired_files)
1058
def get_parent_map(self, revision_ids):
1059
"""See bzrlib.Graph.get_parent_map()."""
1060
return self._make_parents_provider().get_parent_map(revision_ids)
1062
def _get_parent_map_rpc(self, keys):
1063
"""Helper for get_parent_map that performs the RPC."""
1064
medium = self._client._medium
1065
if medium._is_remote_before((1, 2)):
1066
# We already found out that the server can't understand
1067
# Repository.get_parent_map requests, so just fetch the whole
1069
# XXX: Note that this will issue a deprecation warning. This is ok
1070
# :- its because we're working with a deprecated server anyway, and
1071
# the user will almost certainly have seen a warning about the
1072
# server version already.
1073
rg = self.get_revision_graph()
1074
# There is an api discrepency between get_parent_map and
1075
# get_revision_graph. Specifically, a "key:()" pair in
1076
# get_revision_graph just means a node has no parents. For
1077
# "get_parent_map" it means the node is a ghost. So fix up the
1078
# graph to correct this.
1079
# https://bugs.launchpad.net/bzr/+bug/214894
1080
# There is one other "bug" which is that ghosts in
1081
# get_revision_graph() are not returned at all. But we won't worry
1082
# about that for now.
1083
for node_id, parent_ids in rg.iteritems():
1084
if parent_ids == ():
1085
rg[node_id] = (NULL_REVISION,)
1086
rg[NULL_REVISION] = ()
1091
raise ValueError('get_parent_map(None) is not valid')
1092
if NULL_REVISION in keys:
1093
keys.discard(NULL_REVISION)
1094
found_parents = {NULL_REVISION:()}
1096
return found_parents
1099
# TODO(Needs analysis): We could assume that the keys being requested
1100
# from get_parent_map are in a breadth first search, so typically they
1101
# will all be depth N from some common parent, and we don't have to
1102
# have the server iterate from the root parent, but rather from the
1103
# keys we're searching; and just tell the server the keyspace we
1104
# already have; but this may be more traffic again.
1106
# Transform self._parents_map into a search request recipe.
1107
# TODO: Manage this incrementally to avoid covering the same path
1108
# repeatedly. (The server will have to on each request, but the less
1109
# work done the better).
1110
parents_map = self._unstacked_provider.get_cached_map()
1111
if parents_map is None:
1112
# Repository is not locked, so there's no cache.
1114
start_set = set(parents_map)
1115
result_parents = set()
1116
for parents in parents_map.itervalues():
1117
result_parents.update(parents)
1118
stop_keys = result_parents.difference(start_set)
1119
included_keys = start_set.intersection(result_parents)
1120
start_set.difference_update(included_keys)
1121
recipe = (start_set, stop_keys, len(parents_map))
1122
body = self._serialise_search_recipe(recipe)
1123
path = self.bzrdir._path_for_remote_call(self._client)
1125
if type(key) is not str:
1127
"key %r not a plain string" % (key,))
1128
verb = 'Repository.get_parent_map'
1129
args = (path,) + tuple(keys)
1131
response = self._call_with_body_bytes_expecting_body(
1133
except errors.UnknownSmartMethod:
1134
# Server does not support this method, so get the whole graph.
1135
# Worse, we have to force a disconnection, because the server now
1136
# doesn't realise it has a body on the wire to consume, so the
1137
# only way to recover is to abandon the connection.
1139
'Server is too old for fast get_parent_map, reconnecting. '
1140
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1142
# To avoid having to disconnect repeatedly, we keep track of the
1143
# fact the server doesn't understand remote methods added in 1.2.
1144
medium._remember_remote_is_before((1, 2))
1145
return self.get_revision_graph(None)
1146
response_tuple, response_handler = response
1147
if response_tuple[0] not in ['ok']:
1148
response_handler.cancel_read_body()
1149
raise errors.UnexpectedSmartServerResponse(response_tuple)
1150
if response_tuple[0] == 'ok':
1151
coded = bz2.decompress(response_handler.read_body_bytes())
1153
# no revisions found
1155
lines = coded.split('\n')
1158
d = tuple(line.split())
1160
revision_graph[d[0]] = d[1:]
1162
# No parents - so give the Graph result (NULL_REVISION,).
1163
revision_graph[d[0]] = (NULL_REVISION,)
1164
return revision_graph
1167
def get_signature_text(self, revision_id):
1169
return self._real_repository.get_signature_text(revision_id)
1172
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1173
def get_revision_graph_with_ghosts(self, revision_ids=None):
1175
return self._real_repository.get_revision_graph_with_ghosts(
1176
revision_ids=revision_ids)
1179
def get_inventory_xml(self, revision_id):
1181
return self._real_repository.get_inventory_xml(revision_id)
1183
def deserialise_inventory(self, revision_id, xml):
1185
return self._real_repository.deserialise_inventory(revision_id, xml)
1187
def reconcile(self, other=None, thorough=False):
1189
return self._real_repository.reconcile(other=other, thorough=thorough)
1191
def all_revision_ids(self):
1193
return self._real_repository.all_revision_ids()
1196
def get_deltas_for_revisions(self, revisions):
1198
return self._real_repository.get_deltas_for_revisions(revisions)
1201
def get_revision_delta(self, revision_id):
1203
return self._real_repository.get_revision_delta(revision_id)
1206
def revision_trees(self, revision_ids):
1208
return self._real_repository.revision_trees(revision_ids)
1211
def get_revision_reconcile(self, revision_id):
1213
return self._real_repository.get_revision_reconcile(revision_id)
1216
def check(self, revision_ids=None):
1218
return self._real_repository.check(revision_ids=revision_ids)
1220
def copy_content_into(self, destination, revision_id=None):
1222
return self._real_repository.copy_content_into(
1223
destination, revision_id=revision_id)
1225
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1226
# get a tarball of the remote repository, and copy from that into the
1228
from bzrlib import osutils
1230
# TODO: Maybe a progress bar while streaming the tarball?
1231
note("Copying repository content as tarball...")
1232
tar_file = self._get_tarball('bz2')
1233
if tar_file is None:
1235
destination = to_bzrdir.create_repository()
1237
tar = tarfile.open('repository', fileobj=tar_file,
1239
tmpdir = osutils.mkdtemp()
1241
_extract_tar(tar, tmpdir)
1242
tmp_bzrdir = BzrDir.open(tmpdir)
1243
tmp_repo = tmp_bzrdir.open_repository()
1244
tmp_repo.copy_content_into(destination, revision_id)
1246
osutils.rmtree(tmpdir)
1250
# TODO: Suggestion from john: using external tar is much faster than
1251
# python's tarfile library, but it may not work on windows.
1254
def inventories(self):
1255
"""Decorate the real repository for now.
1257
In the long term a full blown network facility is needed to
1258
avoid creating a real repository object locally.
1261
return self._real_repository.inventories
1265
"""Compress the data within the repository.
1267
This is not currently implemented within the smart server.
1270
return self._real_repository.pack()
1273
def revisions(self):
1274
"""Decorate the real repository for now.
1276
In the short term this should become a real object to intercept graph
1279
In the long term a full blown network facility is needed.
1282
return self._real_repository.revisions
1284
def set_make_working_trees(self, new_value):
1286
new_value_str = "True"
1288
new_value_str = "False"
1289
path = self.bzrdir._path_for_remote_call(self._client)
1291
response = self._call(
1292
'Repository.set_make_working_trees', path, new_value_str)
1293
except errors.UnknownSmartMethod:
1295
self._real_repository.set_make_working_trees(new_value)
1297
if response[0] != 'ok':
1298
raise errors.UnexpectedSmartServerResponse(response)
1301
def signatures(self):
1302
"""Decorate the real repository for now.
1304
In the long term a full blown network facility is needed to avoid
1305
creating a real repository object locally.
1308
return self._real_repository.signatures
1311
def sign_revision(self, revision_id, gpg_strategy):
1313
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1317
"""Decorate the real repository for now.
1319
In the long term a full blown network facility is needed to avoid
1320
creating a real repository object locally.
1323
return self._real_repository.texts
1326
def get_revisions(self, revision_ids):
1328
return self._real_repository.get_revisions(revision_ids)
1330
def supports_rich_root(self):
1331
return self._format.rich_root_data
1333
def iter_reverse_revision_history(self, revision_id):
1335
return self._real_repository.iter_reverse_revision_history(revision_id)
1338
def _serializer(self):
1339
return self._format._serializer
1341
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1343
return self._real_repository.store_revision_signature(
1344
gpg_strategy, plaintext, revision_id)
1346
def add_signature_text(self, revision_id, signature):
1348
return self._real_repository.add_signature_text(revision_id, signature)
1350
def has_signature_for_revision_id(self, revision_id):
1352
return self._real_repository.has_signature_for_revision_id(revision_id)
1354
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1356
return self._real_repository.item_keys_introduced_by(revision_ids,
1357
_files_pb=_files_pb)
1359
def revision_graph_can_have_wrong_parents(self):
1360
# The answer depends on the remote repo format.
1362
return self._real_repository.revision_graph_can_have_wrong_parents()
1364
def _find_inconsistent_revision_parents(self):
1366
return self._real_repository._find_inconsistent_revision_parents()
1368
def _check_for_inconsistent_revision_parents(self):
1370
return self._real_repository._check_for_inconsistent_revision_parents()
1372
def _make_parents_provider(self, other=None):
1373
providers = [self._unstacked_provider]
1374
if other is not None:
1375
providers.insert(0, other)
1376
providers.extend(r._make_parents_provider() for r in
1377
self._fallback_repositories)
1378
return graph._StackedParentsProvider(providers)
1380
def _serialise_search_recipe(self, recipe):
1381
"""Serialise a graph search recipe.
1383
:param recipe: A search recipe (start, stop, count).
1384
:return: Serialised bytes.
1386
start_keys = ' '.join(recipe[0])
1387
stop_keys = ' '.join(recipe[1])
1388
count = str(recipe[2])
1389
return '\n'.join((start_keys, stop_keys, count))
1392
path = self.bzrdir._path_for_remote_call(self._client)
1394
response = self._call('PackRepository.autopack', path)
1395
except errors.UnknownSmartMethod:
1397
self._real_repository._pack_collection.autopack()
1399
if self._real_repository is not None:
1400
# Reset the real repository's cache of pack names.
1401
# XXX: At some point we may be able to skip this and just rely on
1402
# the automatic retry logic to do the right thing, but for now we
1403
# err on the side of being correct rather than being optimal.
1404
self._real_repository._pack_collection.reload_pack_names()
1405
if response[0] != 'ok':
1406
raise errors.UnexpectedSmartServerResponse(response)
1409
class RemoteStreamSink(repository.StreamSink):
1411
def _insert_real(self, stream, src_format, resume_tokens):
1412
self.target_repo._ensure_real()
1413
sink = self.target_repo._real_repository._get_sink()
1414
result = sink.insert_stream(stream, src_format, resume_tokens)
1416
self.target_repo.autopack()
1419
def insert_stream(self, stream, src_format, resume_tokens):
1420
repo = self.target_repo
1421
client = repo._client
1422
medium = client._medium
1423
if medium._is_remote_before((1, 13)):
1424
# No possible way this can work.
1425
return self._insert_real(stream, src_format, resume_tokens)
1426
path = repo.bzrdir._path_for_remote_call(client)
1427
if not resume_tokens:
1428
# XXX: Ugly but important for correctness, *will* be fixed during
1429
# 1.13 cycle. Pushing a stream that is interrupted results in a
1430
# fallback to the _real_repositories sink *with a partial stream*.
1431
# Thats bad because we insert less data than bzr expected. To avoid
1432
# this we do a trial push to make sure the verb is accessible, and
1433
# do not fallback when actually pushing the stream. A cleanup patch
1434
# is going to look at rewinding/restarting the stream/partial
1436
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1438
response = client.call_with_body_stream(
1439
('Repository.insert_stream', path, ''), byte_stream)
1440
except errors.UnknownSmartMethod:
1441
medium._remember_remote_is_before((1,13))
1442
return self._insert_real(stream, src_format, resume_tokens)
1443
byte_stream = smart_repo._stream_to_byte_stream(
1445
resume_tokens = ' '.join(resume_tokens)
1446
response = client.call_with_body_stream(
1447
('Repository.insert_stream', path, resume_tokens), byte_stream)
1448
if response[0][0] not in ('ok', 'missing-basis'):
1449
raise errors.UnexpectedSmartServerResponse(response)
1450
if response[0][0] == 'missing-basis':
1451
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1452
resume_tokens = tokens
1453
return resume_tokens, missing_keys
1455
if self.target_repo._real_repository is not None:
1456
collection = getattr(self.target_repo._real_repository,
1457
'_pack_collection', None)
1458
if collection is not None:
1459
collection.reload_pack_names()
1463
class RemoteStreamSource(repository.StreamSource):
1464
"""Stream data from a remote server."""
1466
def get_stream(self, search):
1467
# streaming with fallback repositories is not well defined yet: The
1468
# remote repository cannot see the fallback repositories, and thus
1469
# cannot satisfy the entire search in the general case. Likewise the
1470
# fallback repositories cannot reify the search to determine what they
1471
# should send. It likely needs a return value in the stream listing the
1472
# edge of the search to resume from in fallback repositories.
1473
if self.from_repository._fallback_repositories:
1474
return repository.StreamSource.get_stream(self, search)
1475
repo = self.from_repository
1476
client = repo._client
1477
medium = client._medium
1478
if medium._is_remote_before((1, 13)):
1479
# No possible way this can work.
1480
return repository.StreamSource.get_stream(self, search)
1481
path = repo.bzrdir._path_for_remote_call(client)
1483
recipe = repo._serialise_search_recipe(search._recipe)
1484
response = repo._call_with_body_bytes_expecting_body(
1485
'Repository.get_stream',
1486
(path, self.to_format.network_name()), recipe)
1487
response_tuple, response_handler = response
1488
except errors.UnknownSmartMethod:
1489
medium._remember_remote_is_before((1,13))
1490
return repository.StreamSource.get_stream(self, search)
1491
if response_tuple[0] != 'ok':
1492
raise errors.UnexpectedSmartServerResponse(response_tuple)
1493
byte_stream = response_handler.read_streamed_body()
1494
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1495
if src_format.network_name() != repo._format.network_name():
1496
raise AssertionError(
1497
"Mismatched RemoteRepository and stream src %r, %r" % (
1498
src_format.network_name(), repo._format.network_name()))
1502
class RemoteBranchLockableFiles(LockableFiles):
1503
"""A 'LockableFiles' implementation that talks to a smart server.
1505
This is not a public interface class.
1508
def __init__(self, bzrdir, _client):
1509
self.bzrdir = bzrdir
1510
self._client = _client
1511
self._need_find_modes = True
1512
LockableFiles.__init__(
1513
self, bzrdir.get_branch_transport(None),
1514
'lock', lockdir.LockDir)
1516
def _find_modes(self):
1517
# RemoteBranches don't let the client set the mode of control files.
1518
self._dir_mode = None
1519
self._file_mode = None
1522
class RemoteBranchFormat(branch.BranchFormat):
1525
super(RemoteBranchFormat, self).__init__()
1526
self._matchingbzrdir = RemoteBzrDirFormat()
1527
self._matchingbzrdir.set_branch_format(self)
1528
self._custom_format = None
1530
def __eq__(self, other):
1531
return (isinstance(other, RemoteBranchFormat) and
1532
self.__dict__ == other.__dict__)
1534
def get_format_description(self):
1535
return 'Remote BZR Branch'
1537
def network_name(self):
1538
return self._network_name
1540
def open(self, a_bzrdir):
1541
return a_bzrdir.open_branch()
1543
def _vfs_initialize(self, a_bzrdir):
1544
# Initialisation when using a local bzrdir object, or a non-vfs init
1545
# method is not available on the server.
1546
# self._custom_format is always set - the start of initialize ensures
1548
if isinstance(a_bzrdir, RemoteBzrDir):
1549
a_bzrdir._ensure_real()
1550
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1552
# We assume the bzrdir is parameterised; it may not be.
1553
result = self._custom_format.initialize(a_bzrdir)
1554
if (isinstance(a_bzrdir, RemoteBzrDir) and
1555
not isinstance(result, RemoteBranch)):
1556
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1559
def initialize(self, a_bzrdir):
1560
# 1) get the network name to use.
1561
if self._custom_format:
1562
network_name = self._custom_format.network_name()
1564
# Select the current bzrlib default and ask for that.
1565
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1566
reference_format = reference_bzrdir_format.get_branch_format()
1567
self._custom_format = reference_format
1568
network_name = reference_format.network_name()
1569
# Being asked to create on a non RemoteBzrDir:
1570
if not isinstance(a_bzrdir, RemoteBzrDir):
1571
return self._vfs_initialize(a_bzrdir)
1572
medium = a_bzrdir._client._medium
1573
if medium._is_remote_before((1, 13)):
1574
return self._vfs_initialize(a_bzrdir)
1575
# Creating on a remote bzr dir.
1576
# 2) try direct creation via RPC
1577
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1578
verb = 'BzrDir.create_branch'
1580
response = a_bzrdir._call(verb, path, network_name)
1581
except errors.UnknownSmartMethod:
1582
# Fallback - use vfs methods
1583
return self._vfs_initialize(a_bzrdir)
1584
if response[0] != 'ok':
1585
raise errors.UnexpectedSmartServerResponse(response)
1586
# Turn the response into a RemoteRepository object.
1587
format = RemoteBranchFormat()
1588
format._network_name = response[1]
1589
repo_format = response_tuple_to_repo_format(response[3:])
1590
if response[2] == '':
1591
repo_bzrdir = a_bzrdir
1593
repo_bzrdir = RemoteBzrDir(
1594
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1596
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1597
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1598
format=format, setup_stacking=False)
1599
# XXX: We know this is a new branch, so it must have revno 0, revid
1600
# NULL_REVISION. Creating the branch locked would make this be unable
1601
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1602
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1603
return remote_branch
1605
def supports_tags(self):
1606
# Remote branches might support tags, but we won't know until we
1607
# access the real remote branch.
1611
class RemoteBranch(branch.Branch, _RpcHelper):
1612
"""Branch stored on a server accessed by HPSS RPC.
1614
At the moment most operations are mapped down to simple file operations.
1617
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1618
_client=None, format=None, setup_stacking=True):
1619
"""Create a RemoteBranch instance.
1621
:param real_branch: An optional local implementation of the branch
1622
format, usually accessing the data via the VFS.
1623
:param _client: Private parameter for testing.
1624
:param format: A RemoteBranchFormat object, None to create one
1625
automatically. If supplied it should have a network_name already
1627
:param setup_stacking: If True make an RPC call to determine the
1628
stacked (or not) status of the branch. If False assume the branch
1631
# We intentionally don't call the parent class's __init__, because it
1632
# will try to assign to self.tags, which is a property in this subclass.
1633
# And the parent's __init__ doesn't do much anyway.
1634
self._revision_id_to_revno_cache = None
1635
self._partial_revision_id_to_revno_cache = {}
1636
self._revision_history_cache = None
1637
self._last_revision_info_cache = None
1638
self._merge_sorted_revisions_cache = None
1639
self.bzrdir = remote_bzrdir
1640
if _client is not None:
1641
self._client = _client
1643
self._client = remote_bzrdir._client
1644
self.repository = remote_repository
1645
if real_branch is not None:
1646
self._real_branch = real_branch
1647
# Give the remote repository the matching real repo.
1648
real_repo = self._real_branch.repository
1649
if isinstance(real_repo, RemoteRepository):
1650
real_repo._ensure_real()
1651
real_repo = real_repo._real_repository
1652
self.repository._set_real_repository(real_repo)
1653
# Give the branch the remote repository to let fast-pathing happen.
1654
self._real_branch.repository = self.repository
1656
self._real_branch = None
1657
# Fill out expected attributes of branch for bzrlib api users.
1658
self.base = self.bzrdir.root_transport.base
1659
self._control_files = None
1660
self._lock_mode = None
1661
self._lock_token = None
1662
self._repo_lock_token = None
1663
self._lock_count = 0
1664
self._leave_lock = False
1665
# Setup a format: note that we cannot call _ensure_real until all the
1666
# attributes above are set: This code cannot be moved higher up in this
1669
self._format = RemoteBranchFormat()
1670
if real_branch is not None:
1671
self._format._network_name = \
1672
self._real_branch._format.network_name()
1674
# # XXX: Need to get this from BzrDir.open_branch's return value.
1675
# self._ensure_real()
1676
# self._format._network_name = \
1677
# self._real_branch._format.network_name()
1679
self._format = format
1680
# The base class init is not called, so we duplicate this:
1681
hooks = branch.Branch.hooks['open']
1685
self._setup_stacking()
1687
def _setup_stacking(self):
1688
# configure stacking into the remote repository, by reading it from
1691
fallback_url = self.get_stacked_on_url()
1692
except (errors.NotStacked, errors.UnstackableBranchFormat,
1693
errors.UnstackableRepositoryFormat), e:
1695
# it's relative to this branch...
1696
fallback_url = urlutils.join(self.base, fallback_url)
1697
transports = [self.bzrdir.root_transport]
1698
if self._real_branch is not None:
1699
# The real repository is setup already:
1700
transports.append(self._real_branch._transport)
1701
self.repository.add_fallback_repository(
1702
self.repository._real_repository._fallback_repositories[0])
1704
stacked_on = branch.Branch.open(fallback_url,
1705
possible_transports=transports)
1706
self.repository.add_fallback_repository(stacked_on.repository)
1708
def _get_real_transport(self):
1709
# if we try vfs access, return the real branch's vfs transport
1711
return self._real_branch._transport
1713
_transport = property(_get_real_transport)
1716
return "%s(%s)" % (self.__class__.__name__, self.base)
1720
def _ensure_real(self):
1721
"""Ensure that there is a _real_branch set.
1723
Used before calls to self._real_branch.
1725
if self._real_branch is None:
1726
if not vfs.vfs_enabled():
1727
raise AssertionError('smart server vfs must be enabled '
1728
'to use vfs implementation')
1729
self.bzrdir._ensure_real()
1730
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1731
if self.repository._real_repository is None:
1732
# Give the remote repository the matching real repo.
1733
real_repo = self._real_branch.repository
1734
if isinstance(real_repo, RemoteRepository):
1735
real_repo._ensure_real()
1736
real_repo = real_repo._real_repository
1737
self.repository._set_real_repository(real_repo)
1738
# Give the real branch the remote repository to let fast-pathing
1740
self._real_branch.repository = self.repository
1741
if self._lock_mode == 'r':
1742
self._real_branch.lock_read()
1743
elif self._lock_mode == 'w':
1744
self._real_branch.lock_write(token=self._lock_token)
1746
def _translate_error(self, err, **context):
1747
self.repository._translate_error(err, branch=self, **context)
1749
def _clear_cached_state(self):
1750
super(RemoteBranch, self)._clear_cached_state()
1751
if self._real_branch is not None:
1752
self._real_branch._clear_cached_state()
1754
def _clear_cached_state_of_remote_branch_only(self):
1755
"""Like _clear_cached_state, but doesn't clear the cache of
1758
This is useful when falling back to calling a method of
1759
self._real_branch that changes state. In that case the underlying
1760
branch changes, so we need to invalidate this RemoteBranch's cache of
1761
it. However, there's no need to invalidate the _real_branch's cache
1762
too, in fact doing so might harm performance.
1764
super(RemoteBranch, self)._clear_cached_state()
1767
def control_files(self):
1768
# Defer actually creating RemoteBranchLockableFiles until its needed,
1769
# because it triggers an _ensure_real that we otherwise might not need.
1770
if self._control_files is None:
1771
self._control_files = RemoteBranchLockableFiles(
1772
self.bzrdir, self._client)
1773
return self._control_files
1775
def _get_checkout_format(self):
1777
return self._real_branch._get_checkout_format()
1779
def get_physical_lock_status(self):
1780
"""See Branch.get_physical_lock_status()."""
1781
# should be an API call to the server, as branches must be lockable.
1783
return self._real_branch.get_physical_lock_status()
1785
def get_stacked_on_url(self):
1786
"""Get the URL this branch is stacked against.
1788
:raises NotStacked: If the branch is not stacked.
1789
:raises UnstackableBranchFormat: If the branch does not support
1791
:raises UnstackableRepositoryFormat: If the repository does not support
1795
# there may not be a repository yet, so we can't use
1796
# self._translate_error, so we can't use self._call either.
1797
response = self._client.call('Branch.get_stacked_on_url',
1798
self._remote_path())
1799
except errors.ErrorFromSmartServer, err:
1800
# there may not be a repository yet, so we can't call through
1801
# its _translate_error
1802
_translate_error(err, branch=self)
1803
except errors.UnknownSmartMethod, err:
1805
return self._real_branch.get_stacked_on_url()
1806
if response[0] != 'ok':
1807
raise errors.UnexpectedSmartServerResponse(response)
1810
def lock_read(self):
1811
self.repository.lock_read()
1812
if not self._lock_mode:
1813
self._lock_mode = 'r'
1814
self._lock_count = 1
1815
if self._real_branch is not None:
1816
self._real_branch.lock_read()
1818
self._lock_count += 1
1820
def _remote_lock_write(self, token):
1822
branch_token = repo_token = ''
1824
branch_token = token
1825
repo_token = self.repository.lock_write()
1826
self.repository.unlock()
1827
err_context = {'token': token}
1828
response = self._call(
1829
'Branch.lock_write', self._remote_path(), branch_token,
1830
repo_token or '', **err_context)
1831
if response[0] != 'ok':
1832
raise errors.UnexpectedSmartServerResponse(response)
1833
ok, branch_token, repo_token = response
1834
return branch_token, repo_token
1836
def lock_write(self, token=None):
1837
if not self._lock_mode:
1838
# Lock the branch and repo in one remote call.
1839
remote_tokens = self._remote_lock_write(token)
1840
self._lock_token, self._repo_lock_token = remote_tokens
1841
if not self._lock_token:
1842
raise SmartProtocolError('Remote server did not return a token!')
1843
# Tell the self.repository object that it is locked.
1844
self.repository.lock_write(
1845
self._repo_lock_token, _skip_rpc=True)
1847
if self._real_branch is not None:
1848
self._real_branch.lock_write(token=self._lock_token)
1849
if token is not None:
1850
self._leave_lock = True
1852
self._leave_lock = False
1853
self._lock_mode = 'w'
1854
self._lock_count = 1
1855
elif self._lock_mode == 'r':
1856
raise errors.ReadOnlyTransaction
1858
if token is not None:
1859
# A token was given to lock_write, and we're relocking, so
1860
# check that the given token actually matches the one we
1862
if token != self._lock_token:
1863
raise errors.TokenMismatch(token, self._lock_token)
1864
self._lock_count += 1
1865
# Re-lock the repository too.
1866
self.repository.lock_write(self._repo_lock_token)
1867
return self._lock_token or None
1869
def _unlock(self, branch_token, repo_token):
1870
err_context = {'token': str((branch_token, repo_token))}
1871
response = self._call(
1872
'Branch.unlock', self._remote_path(), branch_token,
1873
repo_token or '', **err_context)
1874
if response == ('ok',):
1876
raise errors.UnexpectedSmartServerResponse(response)
1880
self._lock_count -= 1
1881
if not self._lock_count:
1882
self._clear_cached_state()
1883
mode = self._lock_mode
1884
self._lock_mode = None
1885
if self._real_branch is not None:
1886
if (not self._leave_lock and mode == 'w' and
1887
self._repo_lock_token):
1888
# If this RemoteBranch will remove the physical lock
1889
# for the repository, make sure the _real_branch
1890
# doesn't do it first. (Because the _real_branch's
1891
# repository is set to be the RemoteRepository.)
1892
self._real_branch.repository.leave_lock_in_place()
1893
self._real_branch.unlock()
1895
# Only write-locked branched need to make a remote method
1896
# call to perfom the unlock.
1898
if not self._lock_token:
1899
raise AssertionError('Locked, but no token!')
1900
branch_token = self._lock_token
1901
repo_token = self._repo_lock_token
1902
self._lock_token = None
1903
self._repo_lock_token = None
1904
if not self._leave_lock:
1905
self._unlock(branch_token, repo_token)
1907
self.repository.unlock()
1909
def break_lock(self):
1911
return self._real_branch.break_lock()
1913
def leave_lock_in_place(self):
1914
if not self._lock_token:
1915
raise NotImplementedError(self.leave_lock_in_place)
1916
self._leave_lock = True
1918
def dont_leave_lock_in_place(self):
1919
if not self._lock_token:
1920
raise NotImplementedError(self.dont_leave_lock_in_place)
1921
self._leave_lock = False
1923
def _last_revision_info(self):
1924
response = self._call('Branch.last_revision_info', self._remote_path())
1925
if response[0] != 'ok':
1926
raise SmartProtocolError('unexpected response code %s' % (response,))
1927
revno = int(response[1])
1928
last_revision = response[2]
1929
return (revno, last_revision)
1931
def _gen_revision_history(self):
1932
"""See Branch._gen_revision_history()."""
1933
response_tuple, response_handler = self._call_expecting_body(
1934
'Branch.revision_history', self._remote_path())
1935
if response_tuple[0] != 'ok':
1936
raise errors.UnexpectedSmartServerResponse(response_tuple)
1937
result = response_handler.read_body_bytes().split('\x00')
1942
def _remote_path(self):
1943
return self.bzrdir._path_for_remote_call(self._client)
1945
def _set_last_revision_descendant(self, revision_id, other_branch,
1946
allow_diverged=False, allow_overwrite_descendant=False):
1947
# This performs additional work to meet the hook contract; while its
1948
# undesirable, we have to synthesise the revno to call the hook, and
1949
# not calling the hook is worse as it means changes can't be prevented.
1950
# Having calculated this though, we can't just call into
1951
# set_last_revision_info as a simple call, because there is a set_rh
1952
# hook that some folk may still be using.
1953
old_revno, old_revid = self.last_revision_info()
1954
history = self._lefthand_history(revision_id)
1955
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1956
err_context = {'other_branch': other_branch}
1957
response = self._call('Branch.set_last_revision_ex',
1958
self._remote_path(), self._lock_token, self._repo_lock_token,
1959
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1961
self._clear_cached_state()
1962
if len(response) != 3 and response[0] != 'ok':
1963
raise errors.UnexpectedSmartServerResponse(response)
1964
new_revno, new_revision_id = response[1:]
1965
self._last_revision_info_cache = new_revno, new_revision_id
1966
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1967
if self._real_branch is not None:
1968
cache = new_revno, new_revision_id
1969
self._real_branch._last_revision_info_cache = cache
1971
def _set_last_revision(self, revision_id):
1972
old_revno, old_revid = self.last_revision_info()
1973
# This performs additional work to meet the hook contract; while its
1974
# undesirable, we have to synthesise the revno to call the hook, and
1975
# not calling the hook is worse as it means changes can't be prevented.
1976
# Having calculated this though, we can't just call into
1977
# set_last_revision_info as a simple call, because there is a set_rh
1978
# hook that some folk may still be using.
1979
history = self._lefthand_history(revision_id)
1980
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1981
self._clear_cached_state()
1982
response = self._call('Branch.set_last_revision',
1983
self._remote_path(), self._lock_token, self._repo_lock_token,
1985
if response != ('ok',):
1986
raise errors.UnexpectedSmartServerResponse(response)
1987
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1990
def set_revision_history(self, rev_history):
1991
# Send just the tip revision of the history; the server will generate
1992
# the full history from that. If the revision doesn't exist in this
1993
# branch, NoSuchRevision will be raised.
1994
if rev_history == []:
1997
rev_id = rev_history[-1]
1998
self._set_last_revision(rev_id)
1999
for hook in branch.Branch.hooks['set_rh']:
2000
hook(self, rev_history)
2001
self._cache_revision_history(rev_history)
2003
def get_parent(self):
2005
return self._real_branch.get_parent()
2007
def _get_parent_location(self):
2008
# Used by tests, when checking normalisation of given vs stored paths.
2010
return self._real_branch._get_parent_location()
2012
def set_parent(self, url):
2014
return self._real_branch.set_parent(url)
2016
def _set_parent_location(self, url):
2017
# Used by tests, to poke bad urls into branch configurations
2019
self.set_parent(url)
2022
return self._real_branch._set_parent_location(url)
2024
def set_stacked_on_url(self, stacked_location):
2025
"""Set the URL this branch is stacked against.
2027
:raises UnstackableBranchFormat: If the branch does not support
2029
:raises UnstackableRepositoryFormat: If the repository does not support
2033
return self._real_branch.set_stacked_on_url(stacked_location)
2036
def pull(self, source, overwrite=False, stop_revision=None,
2038
self._clear_cached_state_of_remote_branch_only()
2040
return self._real_branch.pull(
2041
source, overwrite=overwrite, stop_revision=stop_revision,
2042
_override_hook_target=self, **kwargs)
2045
def push(self, target, overwrite=False, stop_revision=None):
2047
return self._real_branch.push(
2048
target, overwrite=overwrite, stop_revision=stop_revision,
2049
_override_hook_source_branch=self)
2051
def is_locked(self):
2052
return self._lock_count >= 1
2055
def revision_id_to_revno(self, revision_id):
2057
return self._real_branch.revision_id_to_revno(revision_id)
2060
def set_last_revision_info(self, revno, revision_id):
2061
# XXX: These should be returned by the set_last_revision_info verb
2062
old_revno, old_revid = self.last_revision_info()
2063
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2064
revision_id = ensure_null(revision_id)
2066
response = self._call('Branch.set_last_revision_info',
2067
self._remote_path(), self._lock_token, self._repo_lock_token,
2068
str(revno), revision_id)
2069
except errors.UnknownSmartMethod:
2071
self._clear_cached_state_of_remote_branch_only()
2072
self._real_branch.set_last_revision_info(revno, revision_id)
2073
self._last_revision_info_cache = revno, revision_id
2075
if response == ('ok',):
2076
self._clear_cached_state()
2077
self._last_revision_info_cache = revno, revision_id
2078
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2079
# Update the _real_branch's cache too.
2080
if self._real_branch is not None:
2081
cache = self._last_revision_info_cache
2082
self._real_branch._last_revision_info_cache = cache
2084
raise errors.UnexpectedSmartServerResponse(response)
2087
def generate_revision_history(self, revision_id, last_rev=None,
2089
medium = self._client._medium
2090
if not medium._is_remote_before((1, 6)):
2091
# Use a smart method for 1.6 and above servers
2093
self._set_last_revision_descendant(revision_id, other_branch,
2094
allow_diverged=True, allow_overwrite_descendant=True)
2096
except errors.UnknownSmartMethod:
2097
medium._remember_remote_is_before((1, 6))
2098
self._clear_cached_state_of_remote_branch_only()
2099
self.set_revision_history(self._lefthand_history(revision_id,
2100
last_rev=last_rev,other_branch=other_branch))
2105
return self._real_branch.tags
2107
def set_push_location(self, location):
2109
return self._real_branch.set_push_location(location)
2112
def _extract_tar(tar, to_dir):
2113
"""Extract all the contents of a tarfile object.
2115
A replacement for extractall, which is not present in python2.4
2118
tar.extract(tarinfo, to_dir)
2121
def _translate_error(err, **context):
2122
"""Translate an ErrorFromSmartServer into a more useful error.
2124
Possible context keys:
2132
If the error from the server doesn't match a known pattern, then
2133
UnknownErrorFromSmartServer is raised.
2137
return context[name]
2138
except KeyError, key_err:
2139
mutter('Missing key %r in context %r', key_err.args[0], context)
2142
"""Get the path from the context if present, otherwise use first error
2146
return context['path']
2147
except KeyError, key_err:
2149
return err.error_args[0]
2150
except IndexError, idx_err:
2152
'Missing key %r in context %r', key_err.args[0], context)
2155
if err.error_verb == 'NoSuchRevision':
2156
raise NoSuchRevision(find('branch'), err.error_args[0])
2157
elif err.error_verb == 'nosuchrevision':
2158
raise NoSuchRevision(find('repository'), err.error_args[0])
2159
elif err.error_tuple == ('nobranch',):
2160
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2161
elif err.error_verb == 'norepository':
2162
raise errors.NoRepositoryPresent(find('bzrdir'))
2163
elif err.error_verb == 'LockContention':
2164
raise errors.LockContention('(remote lock)')
2165
elif err.error_verb == 'UnlockableTransport':
2166
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2167
elif err.error_verb == 'LockFailed':
2168
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2169
elif err.error_verb == 'TokenMismatch':
2170
raise errors.TokenMismatch(find('token'), '(remote token)')
2171
elif err.error_verb == 'Diverged':
2172
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2173
elif err.error_verb == 'TipChangeRejected':
2174
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2175
elif err.error_verb == 'UnstackableBranchFormat':
2176
raise errors.UnstackableBranchFormat(*err.error_args)
2177
elif err.error_verb == 'UnstackableRepositoryFormat':
2178
raise errors.UnstackableRepositoryFormat(*err.error_args)
2179
elif err.error_verb == 'NotStacked':
2180
raise errors.NotStacked(branch=find('branch'))
2181
elif err.error_verb == 'PermissionDenied':
2183
if len(err.error_args) >= 2:
2184
extra = err.error_args[1]
2187
raise errors.PermissionDenied(path, extra=extra)
2188
elif err.error_verb == 'ReadError':
2190
raise errors.ReadError(path)
2191
elif err.error_verb == 'NoSuchFile':
2193
raise errors.NoSuchFile(path)
2194
elif err.error_verb == 'FileExists':
2195
raise errors.FileExists(err.error_args[0])
2196
elif err.error_verb == 'DirectoryNotEmpty':
2197
raise errors.DirectoryNotEmpty(err.error_args[0])
2198
elif err.error_verb == 'ShortReadvError':
2199
args = err.error_args
2200
raise errors.ShortReadvError(
2201
args[0], int(args[1]), int(args[2]), int(args[3]))
2202
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2203
encoding = str(err.error_args[0]) # encoding must always be a string
2204
val = err.error_args[1]
2205
start = int(err.error_args[2])
2206
end = int(err.error_args[3])
2207
reason = str(err.error_args[4]) # reason must always be a string
2208
if val.startswith('u:'):
2209
val = val[2:].decode('utf-8')
2210
elif val.startswith('s:'):
2211
val = val[2:].decode('base64')
2212
if err.error_verb == 'UnicodeDecodeError':
2213
raise UnicodeDecodeError(encoding, val, start, end, reason)
2214
elif err.error_verb == 'UnicodeEncodeError':
2215
raise UnicodeEncodeError(encoding, val, start, end, reason)
2216
elif err.error_verb == 'ReadOnlyError':
2217
raise errors.TransportNotPossible('readonly transport')
2218
raise errors.UnknownErrorFromSmartServer(err)