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
44
from bzrlib.revision import ensure_null, NULL_REVISION
45
from bzrlib.trace import mutter, note, warning
46
from bzrlib.util import bencode
47
from bzrlib.versionedfile import record_to_fulltext_bytes
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)
73
# Note: RemoteBzrDirFormat is in bzrdir.py
75
class RemoteBzrDir(BzrDir, _RpcHelper):
76
"""Control directory on a remote server, accessed via bzr:// or similar."""
78
def __init__(self, transport, format, _client=None):
79
"""Construct a RemoteBzrDir.
81
:param _client: Private parameter for testing. Disables probing and the
84
BzrDir.__init__(self, transport, format)
85
# this object holds a delegated bzrdir that uses file-level operations
86
# to talk to the other side
87
self._real_bzrdir = None
90
medium = transport.get_smart_medium()
91
self._client = client._SmartClient(medium)
93
self._client = _client
96
path = self._path_for_remote_call(self._client)
97
response = self._call('BzrDir.open', path)
98
if response not in [('yes',), ('no',)]:
99
raise errors.UnexpectedSmartServerResponse(response)
100
if response == ('no',):
101
raise errors.NotBranchError(path=transport.base)
103
def _ensure_real(self):
104
"""Ensure that there is a _real_bzrdir set.
106
Used before calls to self._real_bzrdir.
108
if not self._real_bzrdir:
109
self._real_bzrdir = BzrDir.open_from_transport(
110
self.root_transport, _server_formats=False)
112
def _translate_error(self, err, **context):
113
_translate_error(err, bzrdir=self, **context)
115
def cloning_metadir(self, stacked=False):
117
return self._real_bzrdir.cloning_metadir(stacked)
119
def create_repository(self, shared=False):
120
# as per meta1 formats - just delegate to the format object which may
122
result = self._format.repository_format.initialize(self, shared)
123
if not isinstance(result, RemoteRepository):
124
return self.open_repository()
128
def destroy_repository(self):
129
"""See BzrDir.destroy_repository"""
131
self._real_bzrdir.destroy_repository()
133
def create_branch(self):
134
# as per meta1 formats - just delegate to the format object which may
136
real_branch = self._format.get_branch_format().initialize(self)
137
if not isinstance(real_branch, RemoteBranch):
138
return RemoteBranch(self, self.find_repository(), real_branch)
142
def destroy_branch(self):
143
"""See BzrDir.destroy_branch"""
145
self._real_bzrdir.destroy_branch()
147
def create_workingtree(self, revision_id=None, from_branch=None):
148
raise errors.NotLocalUrl(self.transport.base)
150
def find_branch_format(self):
151
"""Find the branch 'format' for this bzrdir.
153
This might be a synthetic object for e.g. RemoteBranch and SVN.
155
b = self.open_branch()
158
def get_branch_reference(self):
159
"""See BzrDir.get_branch_reference()."""
160
path = self._path_for_remote_call(self._client)
161
response = self._call('BzrDir.open_branch', path)
162
if response[0] == 'ok':
163
if response[1] == '':
164
# branch at this location.
167
# a branch reference, use the existing BranchReference logic.
170
raise errors.UnexpectedSmartServerResponse(response)
172
def _get_tree_branch(self):
173
"""See BzrDir._get_tree_branch()."""
174
return None, self.open_branch()
176
def open_branch(self, _unsupported=False):
178
raise NotImplementedError('unsupported flag support not implemented yet.')
179
reference_url = self.get_branch_reference()
180
if reference_url is None:
181
# branch at this location.
182
return RemoteBranch(self, self.find_repository())
184
# a branch reference, use the existing BranchReference logic.
185
format = BranchReferenceFormat()
186
return format.open(self, _found=True, location=reference_url)
188
def open_repository(self):
189
path = self._path_for_remote_call(self._client)
190
verb = 'BzrDir.find_repositoryV2'
192
response = self._call(verb, path)
193
except errors.UnknownSmartMethod:
194
verb = 'BzrDir.find_repository'
195
response = self._call(verb, path)
196
if response[0] != 'ok':
197
raise errors.UnexpectedSmartServerResponse(response)
198
if verb == 'BzrDir.find_repository':
199
# servers that don't support the V2 method don't support external
201
response = response + ('no', )
202
if not (len(response) == 5):
203
raise SmartProtocolError('incorrect response length %s' % (response,))
204
if response[1] == '':
205
format = RemoteRepositoryFormat()
206
format.rich_root_data = (response[2] == 'yes')
207
format.supports_tree_reference = (response[3] == 'yes')
208
# No wire format to check this yet.
209
format.supports_external_lookups = (response[4] == 'yes')
210
# Used to support creating a real format instance when needed.
211
format._creating_bzrdir = self
212
remote_repo = RemoteRepository(self, format)
213
format._creating_repo = remote_repo
216
raise errors.NoRepositoryPresent(self)
218
def open_workingtree(self, recommend_upgrade=True):
220
if self._real_bzrdir.has_workingtree():
221
raise errors.NotLocalUrl(self.root_transport)
223
raise errors.NoWorkingTree(self.root_transport.base)
225
def _path_for_remote_call(self, client):
226
"""Return the path to be used for this bzrdir in a remote call."""
227
return client.remote_path_from_transport(self.root_transport)
229
def get_branch_transport(self, branch_format):
231
return self._real_bzrdir.get_branch_transport(branch_format)
233
def get_repository_transport(self, repository_format):
235
return self._real_bzrdir.get_repository_transport(repository_format)
237
def get_workingtree_transport(self, workingtree_format):
239
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
241
def can_convert_format(self):
242
"""Upgrading of remote bzrdirs is not supported yet."""
245
def needs_format_conversion(self, format=None):
246
"""Upgrading of remote bzrdirs is not supported yet."""
248
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
249
% 'needs_format_conversion(format=None)')
252
def clone(self, url, revision_id=None, force_new_repo=False,
253
preserve_stacking=False):
255
return self._real_bzrdir.clone(url, revision_id=revision_id,
256
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
258
def get_config(self):
260
return self._real_bzrdir.get_config()
263
class RemoteRepositoryFormat(repository.RepositoryFormat):
264
"""Format for repositories accessed over a _SmartClient.
266
Instances of this repository are represented by RemoteRepository
269
The RemoteRepositoryFormat is parameterized during construction
270
to reflect the capabilities of the real, remote format. Specifically
271
the attributes rich_root_data and supports_tree_reference are set
272
on a per instance basis, and are not set (and should not be) at
275
:ivar _custom_format: If set, a specific concrete repository format that
276
will be used when initializing a repository with this
277
RemoteRepositoryFormat.
278
:ivar _creating_repo: If set, the repository object that this
279
RemoteRepositoryFormat was created for: it can be called into
280
to obtain data like the network name.
283
_matchingbzrdir = RemoteBzrDirFormat()
286
repository.RepositoryFormat.__init__(self)
287
self._custom_format = None
288
self._network_name = None
289
self._creating_bzrdir = None
291
def _vfs_initialize(self, a_bzrdir, shared):
292
"""Helper for common code in initialize."""
293
if self._custom_format:
294
# Custom format requested
295
result = self._custom_format.initialize(a_bzrdir, shared=shared)
296
elif self._creating_bzrdir is not None:
297
# Use the format that the repository we were created to back
299
prior_repo = self._creating_bzrdir.open_repository()
300
prior_repo._ensure_real()
301
result = prior_repo._real_repository._format.initialize(
302
a_bzrdir, shared=shared)
304
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
305
# support remote initialization.
306
# We delegate to a real object at this point (as RemoteBzrDir
307
# delegate to the repository format which would lead to infinite
308
# recursion if we just called a_bzrdir.create_repository.
309
a_bzrdir._ensure_real()
310
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
311
if not isinstance(result, RemoteRepository):
312
return self.open(a_bzrdir)
316
def initialize(self, a_bzrdir, shared=False):
317
# Being asked to create on a non RemoteBzrDir:
318
if not isinstance(a_bzrdir, RemoteBzrDir):
319
return self._vfs_initialize(a_bzrdir, shared)
320
medium = a_bzrdir._client._medium
321
if medium._is_remote_before((1, 13)):
322
return self._vfs_initialize(a_bzrdir, shared)
323
# Creating on a remote bzr dir.
324
# 1) get the network name to use.
325
if self._custom_format:
326
network_name = self._custom_format.network_name()
328
# Select the current bzrlib default and ask for that.
329
reference_bzrdir_format = bzrdir.format_registry.get('default')()
330
reference_format = reference_bzrdir_format.repository_format
331
network_name = reference_format.network_name()
332
# 2) try direct creation via RPC
333
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
334
verb = 'BzrDir.create_repository'
340
response = a_bzrdir._call(verb, path, network_name, shared_str)
341
except errors.UnknownSmartMethod:
342
# Fallback - use vfs methods
343
return self._vfs_initialize(a_bzrdir, shared)
345
# Turn the response into a RemoteRepository object.
346
format = RemoteRepositoryFormat()
347
format.rich_root_data = (response[1] == 'yes')
348
format.supports_tree_reference = (response[2] == 'yes')
349
format.supports_external_lookups = (response[3] == 'yes')
350
format._network_name = response[4]
351
# Used to support creating a real format instance when needed.
352
format._creating_bzrdir = a_bzrdir
353
remote_repo = RemoteRepository(a_bzrdir, format)
354
format._creating_repo = remote_repo
357
def open(self, a_bzrdir):
358
if not isinstance(a_bzrdir, RemoteBzrDir):
359
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
360
return a_bzrdir.open_repository()
362
def get_format_description(self):
363
return 'bzr remote repository'
365
def __eq__(self, other):
366
return self.__class__ == other.__class__
368
def check_conversion_target(self, target_format):
369
if self.rich_root_data and not target_format.rich_root_data:
370
raise errors.BadConversionTarget(
371
'Does not support rich root data.', target_format)
372
if (self.supports_tree_reference and
373
not getattr(target_format, 'supports_tree_reference', False)):
374
raise errors.BadConversionTarget(
375
'Does not support nested trees', target_format)
377
def network_name(self):
378
if self._network_name:
379
return self._network_name
380
self._creating_repo._ensure_real()
381
return self._creating_repo._real_repository._format.network_name()
384
def _serializer(self):
385
# We should only be getting asked for the serializer for
386
# RemoteRepositoryFormat objects when the RemoteRepositoryFormat object
387
# is a concrete instance for a RemoteRepository. In this case we know
388
# the creating_repo and can use it to supply the serializer.
389
self._creating_repo._ensure_real()
390
return self._creating_repo._real_repository._format._serializer
393
class RemoteRepository(_RpcHelper):
394
"""Repository accessed over rpc.
396
For the moment most operations are performed using local transport-backed
400
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
401
"""Create a RemoteRepository instance.
403
:param remote_bzrdir: The bzrdir hosting this repository.
404
:param format: The RemoteFormat object to use.
405
:param real_repository: If not None, a local implementation of the
406
repository logic for the repository, usually accessing the data
408
:param _client: Private testing parameter - override the smart client
409
to be used by the repository.
412
self._real_repository = real_repository
414
self._real_repository = None
415
self.bzrdir = remote_bzrdir
417
self._client = remote_bzrdir._client
419
self._client = _client
420
self._format = format
421
self._lock_mode = None
422
self._lock_token = None
424
self._leave_lock = False
425
self._unstacked_provider = graph.CachingParentsProvider(
426
get_parent_map=self._get_parent_map_rpc)
427
self._unstacked_provider.disable_cache()
429
# These depend on the actual remote format, so force them off for
430
# maximum compatibility. XXX: In future these should depend on the
431
# remote repository instance, but this is irrelevant until we perform
432
# reconcile via an RPC call.
433
self._reconcile_does_inventory_gc = False
434
self._reconcile_fixes_text_parents = False
435
self._reconcile_backsup_inventory = False
436
self.base = self.bzrdir.transport.base
437
# Additional places to query for data.
438
self._fallback_repositories = []
441
return "%s(%s)" % (self.__class__.__name__, self.base)
445
def abort_write_group(self, suppress_errors=False):
446
"""Complete a write group on the decorated repository.
448
Smart methods peform operations in a single step so this api
449
is not really applicable except as a compatibility thunk
450
for older plugins that don't use e.g. the CommitBuilder
453
:param suppress_errors: see Repository.abort_write_group.
456
return self._real_repository.abort_write_group(
457
suppress_errors=suppress_errors)
459
def commit_write_group(self):
460
"""Complete a write group on the decorated repository.
462
Smart methods peform operations in a single step so this api
463
is not really applicable except as a compatibility thunk
464
for older plugins that don't use e.g. the CommitBuilder
468
return self._real_repository.commit_write_group()
470
def resume_write_group(self, tokens):
472
return self._real_repository.resume_write_group(tokens)
474
def suspend_write_group(self):
476
return self._real_repository.suspend_write_group()
478
def _ensure_real(self):
479
"""Ensure that there is a _real_repository set.
481
Used before calls to self._real_repository.
483
if self._real_repository is None:
484
self.bzrdir._ensure_real()
485
self._set_real_repository(
486
self.bzrdir._real_bzrdir.open_repository())
488
def _translate_error(self, err, **context):
489
self.bzrdir._translate_error(err, repository=self, **context)
491
def find_text_key_references(self):
492
"""Find the text key references within the repository.
494
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
495
revision_ids. Each altered file-ids has the exact revision_ids that
496
altered it listed explicitly.
497
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
498
to whether they were referred to by the inventory of the
499
revision_id that they contain. The inventory texts from all present
500
revision ids are assessed to generate this report.
503
return self._real_repository.find_text_key_references()
505
def _generate_text_key_index(self):
506
"""Generate a new text key index for the repository.
508
This is an expensive function that will take considerable time to run.
510
:return: A dict mapping (file_id, revision_id) tuples to a list of
511
parents, also (file_id, revision_id) tuples.
514
return self._real_repository._generate_text_key_index()
516
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
517
def get_revision_graph(self, revision_id=None):
518
"""See Repository.get_revision_graph()."""
519
return self._get_revision_graph(revision_id)
521
def _get_revision_graph(self, revision_id):
522
"""Private method for using with old (< 1.2) servers to fallback."""
523
if revision_id is None:
525
elif revision.is_null(revision_id):
528
path = self.bzrdir._path_for_remote_call(self._client)
529
response = self._call_expecting_body(
530
'Repository.get_revision_graph', path, revision_id)
531
response_tuple, response_handler = response
532
if response_tuple[0] != 'ok':
533
raise errors.UnexpectedSmartServerResponse(response_tuple)
534
coded = response_handler.read_body_bytes()
536
# no revisions in this repository!
538
lines = coded.split('\n')
541
d = tuple(line.split())
542
revision_graph[d[0]] = d[1:]
544
return revision_graph
547
"""See Repository._get_sink()."""
548
return RemoteStreamSink(self)
550
def has_revision(self, revision_id):
551
"""See Repository.has_revision()."""
552
if revision_id == NULL_REVISION:
553
# The null revision is always present.
555
path = self.bzrdir._path_for_remote_call(self._client)
556
response = self._call('Repository.has_revision', path, revision_id)
557
if response[0] not in ('yes', 'no'):
558
raise errors.UnexpectedSmartServerResponse(response)
559
if response[0] == 'yes':
561
for fallback_repo in self._fallback_repositories:
562
if fallback_repo.has_revision(revision_id):
566
def has_revisions(self, revision_ids):
567
"""See Repository.has_revisions()."""
568
# FIXME: This does many roundtrips, particularly when there are
569
# fallback repositories. -- mbp 20080905
571
for revision_id in revision_ids:
572
if self.has_revision(revision_id):
573
result.add(revision_id)
576
def has_same_location(self, other):
577
return (self.__class__ == other.__class__ and
578
self.bzrdir.transport.base == other.bzrdir.transport.base)
580
def get_graph(self, other_repository=None):
581
"""Return the graph for this repository format"""
582
parents_provider = self._make_parents_provider(other_repository)
583
return graph.Graph(parents_provider)
585
def gather_stats(self, revid=None, committers=None):
586
"""See Repository.gather_stats()."""
587
path = self.bzrdir._path_for_remote_call(self._client)
588
# revid can be None to indicate no revisions, not just NULL_REVISION
589
if revid is None or revision.is_null(revid):
593
if committers is None or not committers:
594
fmt_committers = 'no'
596
fmt_committers = 'yes'
597
response_tuple, response_handler = self._call_expecting_body(
598
'Repository.gather_stats', path, fmt_revid, fmt_committers)
599
if response_tuple[0] != 'ok':
600
raise errors.UnexpectedSmartServerResponse(response_tuple)
602
body = response_handler.read_body_bytes()
604
for line in body.split('\n'):
607
key, val_text = line.split(':')
608
if key in ('revisions', 'size', 'committers'):
609
result[key] = int(val_text)
610
elif key in ('firstrev', 'latestrev'):
611
values = val_text.split(' ')[1:]
612
result[key] = (float(values[0]), long(values[1]))
616
def find_branches(self, using=False):
617
"""See Repository.find_branches()."""
618
# should be an API call to the server.
620
return self._real_repository.find_branches(using=using)
622
def get_physical_lock_status(self):
623
"""See Repository.get_physical_lock_status()."""
624
# should be an API call to the server.
626
return self._real_repository.get_physical_lock_status()
628
def is_in_write_group(self):
629
"""Return True if there is an open write group.
631
write groups are only applicable locally for the smart server..
633
if self._real_repository:
634
return self._real_repository.is_in_write_group()
637
return self._lock_count >= 1
640
"""See Repository.is_shared()."""
641
path = self.bzrdir._path_for_remote_call(self._client)
642
response = self._call('Repository.is_shared', path)
643
if response[0] not in ('yes', 'no'):
644
raise SmartProtocolError('unexpected response code %s' % (response,))
645
return response[0] == 'yes'
647
def is_write_locked(self):
648
return self._lock_mode == 'w'
651
# wrong eventually - want a local lock cache context
652
if not self._lock_mode:
653
self._lock_mode = 'r'
655
self._unstacked_provider.enable_cache(cache_misses=False)
656
if self._real_repository is not None:
657
self._real_repository.lock_read()
659
self._lock_count += 1
661
def _remote_lock_write(self, token):
662
path = self.bzrdir._path_for_remote_call(self._client)
665
err_context = {'token': token}
666
response = self._call('Repository.lock_write', path, token,
668
if response[0] == 'ok':
672
raise errors.UnexpectedSmartServerResponse(response)
674
def lock_write(self, token=None, _skip_rpc=False):
675
if not self._lock_mode:
677
if self._lock_token is not None:
678
if token != self._lock_token:
679
raise errors.TokenMismatch(token, self._lock_token)
680
self._lock_token = token
682
self._lock_token = self._remote_lock_write(token)
683
# if self._lock_token is None, then this is something like packs or
684
# svn where we don't get to lock the repo, or a weave style repository
685
# where we cannot lock it over the wire and attempts to do so will
687
if self._real_repository is not None:
688
self._real_repository.lock_write(token=self._lock_token)
689
if token is not None:
690
self._leave_lock = True
692
self._leave_lock = False
693
self._lock_mode = 'w'
695
self._unstacked_provider.enable_cache(cache_misses=False)
696
elif self._lock_mode == 'r':
697
raise errors.ReadOnlyError(self)
699
self._lock_count += 1
700
return self._lock_token or None
702
def leave_lock_in_place(self):
703
if not self._lock_token:
704
raise NotImplementedError(self.leave_lock_in_place)
705
self._leave_lock = True
707
def dont_leave_lock_in_place(self):
708
if not self._lock_token:
709
raise NotImplementedError(self.dont_leave_lock_in_place)
710
self._leave_lock = False
712
def _set_real_repository(self, repository):
713
"""Set the _real_repository for this repository.
715
:param repository: The repository to fallback to for non-hpss
716
implemented operations.
718
if self._real_repository is not None:
719
raise AssertionError('_real_repository is already set')
720
if isinstance(repository, RemoteRepository):
721
raise AssertionError()
722
self._real_repository = repository
723
for fb in self._fallback_repositories:
724
self._real_repository.add_fallback_repository(fb)
725
if self._lock_mode == 'w':
726
# if we are already locked, the real repository must be able to
727
# acquire the lock with our token.
728
self._real_repository.lock_write(self._lock_token)
729
elif self._lock_mode == 'r':
730
self._real_repository.lock_read()
732
def start_write_group(self):
733
"""Start a write group on the decorated repository.
735
Smart methods peform operations in a single step so this api
736
is not really applicable except as a compatibility thunk
737
for older plugins that don't use e.g. the CommitBuilder
741
return self._real_repository.start_write_group()
743
def _unlock(self, token):
744
path = self.bzrdir._path_for_remote_call(self._client)
746
# with no token the remote repository is not persistently locked.
748
err_context = {'token': token}
749
response = self._call('Repository.unlock', path, token,
751
if response == ('ok',):
754
raise errors.UnexpectedSmartServerResponse(response)
757
self._lock_count -= 1
758
if self._lock_count > 0:
760
self._unstacked_provider.disable_cache()
761
old_mode = self._lock_mode
762
self._lock_mode = None
764
# The real repository is responsible at present for raising an
765
# exception if it's in an unfinished write group. However, it
766
# normally will *not* actually remove the lock from disk - that's
767
# done by the server on receiving the Repository.unlock call.
768
# This is just to let the _real_repository stay up to date.
769
if self._real_repository is not None:
770
self._real_repository.unlock()
772
# The rpc-level lock should be released even if there was a
773
# problem releasing the vfs-based lock.
775
# Only write-locked repositories need to make a remote method
776
# call to perfom the unlock.
777
old_token = self._lock_token
778
self._lock_token = None
779
if not self._leave_lock:
780
self._unlock(old_token)
782
def break_lock(self):
783
# should hand off to the network
785
return self._real_repository.break_lock()
787
def _get_tarball(self, compression):
788
"""Return a TemporaryFile containing a repository tarball.
790
Returns None if the server does not support sending tarballs.
793
path = self.bzrdir._path_for_remote_call(self._client)
795
response, protocol = self._call_expecting_body(
796
'Repository.tarball', path, compression)
797
except errors.UnknownSmartMethod:
798
protocol.cancel_read_body()
800
if response[0] == 'ok':
801
# Extract the tarball and return it
802
t = tempfile.NamedTemporaryFile()
803
# TODO: rpc layer should read directly into it...
804
t.write(protocol.read_body_bytes())
807
raise errors.UnexpectedSmartServerResponse(response)
809
def sprout(self, to_bzrdir, revision_id=None):
810
# TODO: Option to control what format is created?
812
dest_repo = self._real_repository._format.initialize(to_bzrdir,
814
dest_repo.fetch(self, revision_id=revision_id)
817
### These methods are just thin shims to the VFS object for now.
819
def revision_tree(self, revision_id):
821
return self._real_repository.revision_tree(revision_id)
823
def get_serializer_format(self):
825
return self._real_repository.get_serializer_format()
827
def get_commit_builder(self, branch, parents, config, timestamp=None,
828
timezone=None, committer=None, revprops=None,
830
# FIXME: It ought to be possible to call this without immediately
831
# triggering _ensure_real. For now it's the easiest thing to do.
833
real_repo = self._real_repository
834
builder = real_repo.get_commit_builder(branch, parents,
835
config, timestamp=timestamp, timezone=timezone,
836
committer=committer, revprops=revprops, revision_id=revision_id)
839
def add_fallback_repository(self, repository):
840
"""Add a repository to use for looking up data not held locally.
842
:param repository: A repository.
844
# XXX: At the moment the RemoteRepository will allow fallbacks
845
# unconditionally - however, a _real_repository will usually exist,
846
# and may raise an error if it's not accommodated by the underlying
847
# format. Eventually we should check when opening the repository
848
# whether it's willing to allow them or not.
850
# We need to accumulate additional repositories here, to pass them in
852
self._fallback_repositories.append(repository)
853
# They are also seen by the fallback repository. If it doesn't exist
854
# yet they'll be added then. This implicitly copies them.
857
def add_inventory(self, revid, inv, parents):
859
return self._real_repository.add_inventory(revid, inv, parents)
861
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
864
return self._real_repository.add_inventory_by_delta(basis_revision_id,
865
delta, new_revision_id, parents)
867
def add_revision(self, rev_id, rev, inv=None, config=None):
869
return self._real_repository.add_revision(
870
rev_id, rev, inv=inv, config=config)
873
def get_inventory(self, revision_id):
875
return self._real_repository.get_inventory(revision_id)
877
def iter_inventories(self, revision_ids):
879
return self._real_repository.iter_inventories(revision_ids)
882
def get_revision(self, revision_id):
884
return self._real_repository.get_revision(revision_id)
886
def get_transaction(self):
888
return self._real_repository.get_transaction()
891
def clone(self, a_bzrdir, revision_id=None):
893
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
895
def make_working_trees(self):
896
"""See Repository.make_working_trees"""
898
return self._real_repository.make_working_trees()
900
def revision_ids_to_search_result(self, result_set):
901
"""Convert a set of revision ids to a graph SearchResult."""
902
result_parents = set()
903
for parents in self.get_graph().get_parent_map(
904
result_set).itervalues():
905
result_parents.update(parents)
906
included_keys = result_set.intersection(result_parents)
907
start_keys = result_set.difference(included_keys)
908
exclude_keys = result_parents.difference(result_set)
909
result = graph.SearchResult(start_keys, exclude_keys,
910
len(result_set), result_set)
914
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
915
"""Return the revision ids that other has that this does not.
917
These are returned in topological order.
919
revision_id: only return revision ids included by revision_id.
921
return repository.InterRepository.get(
922
other, self).search_missing_revision_ids(revision_id, find_ghosts)
924
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
925
# Not delegated to _real_repository so that InterRepository.get has a
926
# chance to find an InterRepository specialised for RemoteRepository.
927
if self.has_same_location(source):
928
# check that last_revision is in 'from' and then return a
930
if (revision_id is not None and
931
not revision.is_null(revision_id)):
932
self.get_revision(revision_id)
934
inter = repository.InterRepository.get(source, self)
936
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
937
except NotImplementedError:
938
raise errors.IncompatibleRepositories(source, self)
940
def create_bundle(self, target, base, fileobj, format=None):
942
self._real_repository.create_bundle(target, base, fileobj, format)
945
def get_ancestry(self, revision_id, topo_sorted=True):
947
return self._real_repository.get_ancestry(revision_id, topo_sorted)
949
def fileids_altered_by_revision_ids(self, revision_ids):
951
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
953
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
955
return self._real_repository._get_versioned_file_checker(
956
revisions, revision_versions_cache)
958
def iter_files_bytes(self, desired_files):
959
"""See Repository.iter_file_bytes.
962
return self._real_repository.iter_files_bytes(desired_files)
965
def _fetch_order(self):
966
"""Decorate the real repository for now.
968
In the long term getting this back from the remote repository as part
969
of open would be more efficient.
972
return self._real_repository._fetch_order
975
def _fetch_uses_deltas(self):
976
"""Decorate the real repository for now.
978
In the long term getting this back from the remote repository as part
979
of open would be more efficient.
982
return self._real_repository._fetch_uses_deltas
985
def _fetch_reconcile(self):
986
"""Decorate the real repository for now.
988
In the long term getting this back from the remote repository as part
989
of open would be more efficient.
992
return self._real_repository._fetch_reconcile
994
def get_parent_map(self, revision_ids):
995
"""See bzrlib.Graph.get_parent_map()."""
996
return self._make_parents_provider().get_parent_map(revision_ids)
998
def _get_parent_map_rpc(self, keys):
999
"""Helper for get_parent_map that performs the RPC."""
1000
medium = self._client._medium
1001
if medium._is_remote_before((1, 2)):
1002
# We already found out that the server can't understand
1003
# Repository.get_parent_map requests, so just fetch the whole
1005
# XXX: Note that this will issue a deprecation warning. This is ok
1006
# :- its because we're working with a deprecated server anyway, and
1007
# the user will almost certainly have seen a warning about the
1008
# server version already.
1009
rg = self.get_revision_graph()
1010
# There is an api discrepency between get_parent_map and
1011
# get_revision_graph. Specifically, a "key:()" pair in
1012
# get_revision_graph just means a node has no parents. For
1013
# "get_parent_map" it means the node is a ghost. So fix up the
1014
# graph to correct this.
1015
# https://bugs.launchpad.net/bzr/+bug/214894
1016
# There is one other "bug" which is that ghosts in
1017
# get_revision_graph() are not returned at all. But we won't worry
1018
# about that for now.
1019
for node_id, parent_ids in rg.iteritems():
1020
if parent_ids == ():
1021
rg[node_id] = (NULL_REVISION,)
1022
rg[NULL_REVISION] = ()
1027
raise ValueError('get_parent_map(None) is not valid')
1028
if NULL_REVISION in keys:
1029
keys.discard(NULL_REVISION)
1030
found_parents = {NULL_REVISION:()}
1032
return found_parents
1035
# TODO(Needs analysis): We could assume that the keys being requested
1036
# from get_parent_map are in a breadth first search, so typically they
1037
# will all be depth N from some common parent, and we don't have to
1038
# have the server iterate from the root parent, but rather from the
1039
# keys we're searching; and just tell the server the keyspace we
1040
# already have; but this may be more traffic again.
1042
# Transform self._parents_map into a search request recipe.
1043
# TODO: Manage this incrementally to avoid covering the same path
1044
# repeatedly. (The server will have to on each request, but the less
1045
# work done the better).
1046
parents_map = self._unstacked_provider.get_cached_map()
1047
if parents_map is None:
1048
# Repository is not locked, so there's no cache.
1050
start_set = set(parents_map)
1051
result_parents = set()
1052
for parents in parents_map.itervalues():
1053
result_parents.update(parents)
1054
stop_keys = result_parents.difference(start_set)
1055
included_keys = start_set.intersection(result_parents)
1056
start_set.difference_update(included_keys)
1057
recipe = (start_set, stop_keys, len(parents_map))
1058
body = self._serialise_search_recipe(recipe)
1059
path = self.bzrdir._path_for_remote_call(self._client)
1061
if type(key) is not str:
1063
"key %r not a plain string" % (key,))
1064
verb = 'Repository.get_parent_map'
1065
args = (path,) + tuple(keys)
1067
response = self._call_with_body_bytes_expecting_body(
1069
except errors.UnknownSmartMethod:
1070
# Server does not support this method, so get the whole graph.
1071
# Worse, we have to force a disconnection, because the server now
1072
# doesn't realise it has a body on the wire to consume, so the
1073
# only way to recover is to abandon the connection.
1075
'Server is too old for fast get_parent_map, reconnecting. '
1076
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1078
# To avoid having to disconnect repeatedly, we keep track of the
1079
# fact the server doesn't understand remote methods added in 1.2.
1080
medium._remember_remote_is_before((1, 2))
1081
return self.get_revision_graph(None)
1082
response_tuple, response_handler = response
1083
if response_tuple[0] not in ['ok']:
1084
response_handler.cancel_read_body()
1085
raise errors.UnexpectedSmartServerResponse(response_tuple)
1086
if response_tuple[0] == 'ok':
1087
coded = bz2.decompress(response_handler.read_body_bytes())
1089
# no revisions found
1091
lines = coded.split('\n')
1094
d = tuple(line.split())
1096
revision_graph[d[0]] = d[1:]
1098
# No parents - so give the Graph result (NULL_REVISION,).
1099
revision_graph[d[0]] = (NULL_REVISION,)
1100
return revision_graph
1103
def get_signature_text(self, revision_id):
1105
return self._real_repository.get_signature_text(revision_id)
1108
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1109
def get_revision_graph_with_ghosts(self, revision_ids=None):
1111
return self._real_repository.get_revision_graph_with_ghosts(
1112
revision_ids=revision_ids)
1115
def get_inventory_xml(self, revision_id):
1117
return self._real_repository.get_inventory_xml(revision_id)
1119
def deserialise_inventory(self, revision_id, xml):
1121
return self._real_repository.deserialise_inventory(revision_id, xml)
1123
def reconcile(self, other=None, thorough=False):
1125
return self._real_repository.reconcile(other=other, thorough=thorough)
1127
def all_revision_ids(self):
1129
return self._real_repository.all_revision_ids()
1132
def get_deltas_for_revisions(self, revisions):
1134
return self._real_repository.get_deltas_for_revisions(revisions)
1137
def get_revision_delta(self, revision_id):
1139
return self._real_repository.get_revision_delta(revision_id)
1142
def revision_trees(self, revision_ids):
1144
return self._real_repository.revision_trees(revision_ids)
1147
def get_revision_reconcile(self, revision_id):
1149
return self._real_repository.get_revision_reconcile(revision_id)
1152
def check(self, revision_ids=None):
1154
return self._real_repository.check(revision_ids=revision_ids)
1156
def copy_content_into(self, destination, revision_id=None):
1158
return self._real_repository.copy_content_into(
1159
destination, revision_id=revision_id)
1161
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1162
# get a tarball of the remote repository, and copy from that into the
1164
from bzrlib import osutils
1166
# TODO: Maybe a progress bar while streaming the tarball?
1167
note("Copying repository content as tarball...")
1168
tar_file = self._get_tarball('bz2')
1169
if tar_file is None:
1171
destination = to_bzrdir.create_repository()
1173
tar = tarfile.open('repository', fileobj=tar_file,
1175
tmpdir = osutils.mkdtemp()
1177
_extract_tar(tar, tmpdir)
1178
tmp_bzrdir = BzrDir.open(tmpdir)
1179
tmp_repo = tmp_bzrdir.open_repository()
1180
tmp_repo.copy_content_into(destination, revision_id)
1182
osutils.rmtree(tmpdir)
1186
# TODO: Suggestion from john: using external tar is much faster than
1187
# python's tarfile library, but it may not work on windows.
1190
def inventories(self):
1191
"""Decorate the real repository for now.
1193
In the long term a full blown network facility is needed to
1194
avoid creating a real repository object locally.
1197
return self._real_repository.inventories
1201
"""Compress the data within the repository.
1203
This is not currently implemented within the smart server.
1206
return self._real_repository.pack()
1209
def revisions(self):
1210
"""Decorate the real repository for now.
1212
In the short term this should become a real object to intercept graph
1215
In the long term a full blown network facility is needed.
1218
return self._real_repository.revisions
1220
def set_make_working_trees(self, new_value):
1222
new_value_str = "True"
1224
new_value_str = "False"
1225
path = self.bzrdir._path_for_remote_call(self._client)
1227
response = self._call(
1228
'Repository.set_make_working_trees', path, new_value_str)
1229
except errors.UnknownSmartMethod:
1231
self._real_repository.set_make_working_trees(new_value)
1233
if response[0] != 'ok':
1234
raise errors.UnexpectedSmartServerResponse(response)
1237
def signatures(self):
1238
"""Decorate the real repository for now.
1240
In the long term a full blown network facility is needed to avoid
1241
creating a real repository object locally.
1244
return self._real_repository.signatures
1247
def sign_revision(self, revision_id, gpg_strategy):
1249
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1253
"""Decorate the real repository for now.
1255
In the long term a full blown network facility is needed to avoid
1256
creating a real repository object locally.
1259
return self._real_repository.texts
1262
def get_revisions(self, revision_ids):
1264
return self._real_repository.get_revisions(revision_ids)
1266
def supports_rich_root(self):
1268
return self._real_repository.supports_rich_root()
1270
def iter_reverse_revision_history(self, revision_id):
1272
return self._real_repository.iter_reverse_revision_history(revision_id)
1275
def _serializer(self):
1277
return self._real_repository._serializer
1279
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1281
return self._real_repository.store_revision_signature(
1282
gpg_strategy, plaintext, revision_id)
1284
def add_signature_text(self, revision_id, signature):
1286
return self._real_repository.add_signature_text(revision_id, signature)
1288
def has_signature_for_revision_id(self, revision_id):
1290
return self._real_repository.has_signature_for_revision_id(revision_id)
1292
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1294
return self._real_repository.item_keys_introduced_by(revision_ids,
1295
_files_pb=_files_pb)
1297
def revision_graph_can_have_wrong_parents(self):
1298
# The answer depends on the remote repo format.
1300
return self._real_repository.revision_graph_can_have_wrong_parents()
1302
def _find_inconsistent_revision_parents(self):
1304
return self._real_repository._find_inconsistent_revision_parents()
1306
def _check_for_inconsistent_revision_parents(self):
1308
return self._real_repository._check_for_inconsistent_revision_parents()
1310
def _make_parents_provider(self, other=None):
1311
providers = [self._unstacked_provider]
1312
if other is not None:
1313
providers.insert(0, other)
1314
providers.extend(r._make_parents_provider() for r in
1315
self._fallback_repositories)
1316
return graph._StackedParentsProvider(providers)
1318
def _serialise_search_recipe(self, recipe):
1319
"""Serialise a graph search recipe.
1321
:param recipe: A search recipe (start, stop, count).
1322
:return: Serialised bytes.
1324
start_keys = ' '.join(recipe[0])
1325
stop_keys = ' '.join(recipe[1])
1326
count = str(recipe[2])
1327
return '\n'.join((start_keys, stop_keys, count))
1330
path = self.bzrdir._path_for_remote_call(self._client)
1332
response = self._call('PackRepository.autopack', path)
1333
except errors.UnknownSmartMethod:
1335
self._real_repository._pack_collection.autopack()
1337
if self._real_repository is not None:
1338
# Reset the real repository's cache of pack names.
1339
# XXX: At some point we may be able to skip this and just rely on
1340
# the automatic retry logic to do the right thing, but for now we
1341
# err on the side of being correct rather than being optimal.
1342
self._real_repository._pack_collection.reload_pack_names()
1343
if response[0] != 'ok':
1344
raise errors.UnexpectedSmartServerResponse(response)
1347
class RemoteStreamSink(repository.StreamSink):
1349
def __init__(self, target_repo):
1350
repository.StreamSink.__init__(self, target_repo)
1351
self._resume_tokens = []
1353
def _insert_real(self, stream, src_format):
1354
self.target_repo._ensure_real()
1355
sink = self.target_repo._real_repository._get_sink()
1356
result = sink.insert_stream(stream, src_format)
1358
self.target_repo.autopack()
1361
def insert_stream(self, stream, src_format):
1362
repo = self.target_repo
1363
client = repo._client
1364
medium = client._medium
1365
if medium._is_remote_before((1, 13)):
1366
# No possible way this can work.
1367
return self._insert_real(stream, src_format)
1368
path = repo.bzrdir._path_for_remote_call(client)
1369
if not self._resume_tokens:
1370
# XXX: Ugly but important for correctness, *will* be fixed during
1371
# 1.13 cycle. Pushing a stream that is interrupted results in a
1372
# fallback to the _real_repositories sink *with a partial stream*.
1373
# Thats bad because we insert less data than bzr expected. To avoid
1374
# this we do a trial push to make sure the verb is accessible, and
1375
# do not fallback when actually pushing the stream. A cleanup patch
1376
# is going to look at rewinding/restarting the stream/partial
1378
byte_stream = self._stream_to_byte_stream([], src_format)
1381
response = client.call_with_body_stream(
1382
('Repository.insert_stream', path, resume_tokens), byte_stream)
1383
except errors.UnknownSmartMethod:
1384
medium._remember_remote_is_before((1,13))
1385
return self._insert_real(stream, src_format)
1386
byte_stream = self._stream_to_byte_stream(stream, src_format)
1387
resume_tokens = ' '.join(self._resume_tokens)
1388
response = client.call_with_body_stream(
1389
('Repository.insert_stream', path, resume_tokens), byte_stream)
1390
if response[0][0] not in ('ok', 'missing-basis'):
1391
raise errors.UnexpectedSmartServerResponse(response)
1392
if response[0][0] == 'missing-basis':
1393
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1394
self._resume_tokens = tokens
1397
if self.target_repo._real_repository is not None:
1398
collection = getattr(self.target_repo._real_repository,
1399
'_pack_collection', None)
1400
if collection is not None:
1401
collection.reload_pack_names()
1404
def _stream_to_byte_stream(self, stream, src_format):
1406
pack_writer = pack.ContainerWriter(bytes.append)
1408
pack_writer.add_bytes_record(src_format.network_name(), '')
1410
def get_adapter(adapter_key):
1412
return adapters[adapter_key]
1414
adapter_factory = adapter_registry.get(adapter_key)
1415
adapter = adapter_factory(self)
1416
adapters[adapter_key] = adapter
1418
for substream_type, substream in stream:
1419
for record in substream:
1420
if record.storage_kind in ('chunked', 'fulltext'):
1421
serialised = record_to_fulltext_bytes(record)
1423
serialised = record.get_bytes_as(record.storage_kind)
1424
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1433
class RemoteBranchLockableFiles(LockableFiles):
1434
"""A 'LockableFiles' implementation that talks to a smart server.
1436
This is not a public interface class.
1439
def __init__(self, bzrdir, _client):
1440
self.bzrdir = bzrdir
1441
self._client = _client
1442
self._need_find_modes = True
1443
LockableFiles.__init__(
1444
self, bzrdir.get_branch_transport(None),
1445
'lock', lockdir.LockDir)
1447
def _find_modes(self):
1448
# RemoteBranches don't let the client set the mode of control files.
1449
self._dir_mode = None
1450
self._file_mode = None
1453
class RemoteBranchFormat(branch.BranchFormat):
1456
super(RemoteBranchFormat, self).__init__()
1457
self._matchingbzrdir = RemoteBzrDirFormat()
1458
self._matchingbzrdir.set_branch_format(self)
1460
def __eq__(self, other):
1461
return (isinstance(other, RemoteBranchFormat) and
1462
self.__dict__ == other.__dict__)
1464
def get_format_description(self):
1465
return 'Remote BZR Branch'
1467
def get_format_string(self):
1468
return 'Remote BZR Branch'
1470
def open(self, a_bzrdir):
1471
return a_bzrdir.open_branch()
1473
def initialize(self, a_bzrdir):
1474
# Delegate to a _real object here - the RemoteBzrDir format now
1475
# supports delegating to parameterised branch formats and as such
1476
# this RemoteBranchFormat method is only called when no specific format
1478
if not isinstance(a_bzrdir, RemoteBzrDir):
1479
result = a_bzrdir.create_branch()
1481
a_bzrdir._ensure_real()
1482
result = a_bzrdir._real_bzrdir.create_branch()
1483
if not isinstance(result, RemoteBranch):
1484
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1487
def supports_tags(self):
1488
# Remote branches might support tags, but we won't know until we
1489
# access the real remote branch.
1493
class RemoteBranch(branch.Branch, _RpcHelper):
1494
"""Branch stored on a server accessed by HPSS RPC.
1496
At the moment most operations are mapped down to simple file operations.
1499
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1501
"""Create a RemoteBranch instance.
1503
:param real_branch: An optional local implementation of the branch
1504
format, usually accessing the data via the VFS.
1505
:param _client: Private parameter for testing.
1507
# We intentionally don't call the parent class's __init__, because it
1508
# will try to assign to self.tags, which is a property in this subclass.
1509
# And the parent's __init__ doesn't do much anyway.
1510
self._revision_id_to_revno_cache = None
1511
self._partial_revision_id_to_revno_cache = {}
1512
self._revision_history_cache = None
1513
self._last_revision_info_cache = None
1514
self._merge_sorted_revisions_cache = None
1515
self.bzrdir = remote_bzrdir
1516
if _client is not None:
1517
self._client = _client
1519
self._client = remote_bzrdir._client
1520
self.repository = remote_repository
1521
if real_branch is not None:
1522
self._real_branch = real_branch
1523
# Give the remote repository the matching real repo.
1524
real_repo = self._real_branch.repository
1525
if isinstance(real_repo, RemoteRepository):
1526
real_repo._ensure_real()
1527
real_repo = real_repo._real_repository
1528
self.repository._set_real_repository(real_repo)
1529
# Give the branch the remote repository to let fast-pathing happen.
1530
self._real_branch.repository = self.repository
1532
self._real_branch = None
1533
# Fill out expected attributes of branch for bzrlib api users.
1534
self._format = RemoteBranchFormat()
1535
self.base = self.bzrdir.root_transport.base
1536
self._control_files = None
1537
self._lock_mode = None
1538
self._lock_token = None
1539
self._repo_lock_token = None
1540
self._lock_count = 0
1541
self._leave_lock = False
1542
# The base class init is not called, so we duplicate this:
1543
hooks = branch.Branch.hooks['open']
1546
self._setup_stacking()
1548
def _setup_stacking(self):
1549
# configure stacking into the remote repository, by reading it from
1552
fallback_url = self.get_stacked_on_url()
1553
except (errors.NotStacked, errors.UnstackableBranchFormat,
1554
errors.UnstackableRepositoryFormat), e:
1556
# it's relative to this branch...
1557
fallback_url = urlutils.join(self.base, fallback_url)
1558
transports = [self.bzrdir.root_transport]
1559
if self._real_branch is not None:
1560
transports.append(self._real_branch._transport)
1561
stacked_on = branch.Branch.open(fallback_url,
1562
possible_transports=transports)
1563
self.repository.add_fallback_repository(stacked_on.repository)
1565
def _get_real_transport(self):
1566
# if we try vfs access, return the real branch's vfs transport
1568
return self._real_branch._transport
1570
_transport = property(_get_real_transport)
1573
return "%s(%s)" % (self.__class__.__name__, self.base)
1577
def _ensure_real(self):
1578
"""Ensure that there is a _real_branch set.
1580
Used before calls to self._real_branch.
1582
if self._real_branch is None:
1583
if not vfs.vfs_enabled():
1584
raise AssertionError('smart server vfs must be enabled '
1585
'to use vfs implementation')
1586
self.bzrdir._ensure_real()
1587
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1588
if self.repository._real_repository is None:
1589
# Give the remote repository the matching real repo.
1590
real_repo = self._real_branch.repository
1591
if isinstance(real_repo, RemoteRepository):
1592
real_repo._ensure_real()
1593
real_repo = real_repo._real_repository
1594
self.repository._set_real_repository(real_repo)
1595
# Give the real branch the remote repository to let fast-pathing
1597
self._real_branch.repository = self.repository
1598
if self._lock_mode == 'r':
1599
self._real_branch.lock_read()
1600
elif self._lock_mode == 'w':
1601
self._real_branch.lock_write(token=self._lock_token)
1603
def _translate_error(self, err, **context):
1604
self.repository._translate_error(err, branch=self, **context)
1606
def _clear_cached_state(self):
1607
super(RemoteBranch, self)._clear_cached_state()
1608
if self._real_branch is not None:
1609
self._real_branch._clear_cached_state()
1611
def _clear_cached_state_of_remote_branch_only(self):
1612
"""Like _clear_cached_state, but doesn't clear the cache of
1615
This is useful when falling back to calling a method of
1616
self._real_branch that changes state. In that case the underlying
1617
branch changes, so we need to invalidate this RemoteBranch's cache of
1618
it. However, there's no need to invalidate the _real_branch's cache
1619
too, in fact doing so might harm performance.
1621
super(RemoteBranch, self)._clear_cached_state()
1624
def control_files(self):
1625
# Defer actually creating RemoteBranchLockableFiles until its needed,
1626
# because it triggers an _ensure_real that we otherwise might not need.
1627
if self._control_files is None:
1628
self._control_files = RemoteBranchLockableFiles(
1629
self.bzrdir, self._client)
1630
return self._control_files
1632
def _get_checkout_format(self):
1634
return self._real_branch._get_checkout_format()
1636
def get_physical_lock_status(self):
1637
"""See Branch.get_physical_lock_status()."""
1638
# should be an API call to the server, as branches must be lockable.
1640
return self._real_branch.get_physical_lock_status()
1642
def get_stacked_on_url(self):
1643
"""Get the URL this branch is stacked against.
1645
:raises NotStacked: If the branch is not stacked.
1646
:raises UnstackableBranchFormat: If the branch does not support
1648
:raises UnstackableRepositoryFormat: If the repository does not support
1652
# there may not be a repository yet, so we can't use
1653
# self._translate_error, so we can't use self._call either.
1654
response = self._client.call('Branch.get_stacked_on_url',
1655
self._remote_path())
1656
except errors.ErrorFromSmartServer, err:
1657
# there may not be a repository yet, so we can't call through
1658
# its _translate_error
1659
_translate_error(err, branch=self)
1660
except errors.UnknownSmartMethod, err:
1662
return self._real_branch.get_stacked_on_url()
1663
if response[0] != 'ok':
1664
raise errors.UnexpectedSmartServerResponse(response)
1667
def lock_read(self):
1668
self.repository.lock_read()
1669
if not self._lock_mode:
1670
self._lock_mode = 'r'
1671
self._lock_count = 1
1672
if self._real_branch is not None:
1673
self._real_branch.lock_read()
1675
self._lock_count += 1
1677
def _remote_lock_write(self, token):
1679
branch_token = repo_token = ''
1681
branch_token = token
1682
repo_token = self.repository.lock_write()
1683
self.repository.unlock()
1684
err_context = {'token': token}
1685
response = self._call(
1686
'Branch.lock_write', self._remote_path(), branch_token,
1687
repo_token or '', **err_context)
1688
if response[0] != 'ok':
1689
raise errors.UnexpectedSmartServerResponse(response)
1690
ok, branch_token, repo_token = response
1691
return branch_token, repo_token
1693
def lock_write(self, token=None):
1694
if not self._lock_mode:
1695
# Lock the branch and repo in one remote call.
1696
remote_tokens = self._remote_lock_write(token)
1697
self._lock_token, self._repo_lock_token = remote_tokens
1698
if not self._lock_token:
1699
raise SmartProtocolError('Remote server did not return a token!')
1700
# Tell the self.repository object that it is locked.
1701
self.repository.lock_write(
1702
self._repo_lock_token, _skip_rpc=True)
1704
if self._real_branch is not None:
1705
self._real_branch.lock_write(token=self._lock_token)
1706
if token is not None:
1707
self._leave_lock = True
1709
self._leave_lock = False
1710
self._lock_mode = 'w'
1711
self._lock_count = 1
1712
elif self._lock_mode == 'r':
1713
raise errors.ReadOnlyTransaction
1715
if token is not None:
1716
# A token was given to lock_write, and we're relocking, so
1717
# check that the given token actually matches the one we
1719
if token != self._lock_token:
1720
raise errors.TokenMismatch(token, self._lock_token)
1721
self._lock_count += 1
1722
# Re-lock the repository too.
1723
self.repository.lock_write(self._repo_lock_token)
1724
return self._lock_token or None
1726
def _unlock(self, branch_token, repo_token):
1727
err_context = {'token': str((branch_token, repo_token))}
1728
response = self._call(
1729
'Branch.unlock', self._remote_path(), branch_token,
1730
repo_token or '', **err_context)
1731
if response == ('ok',):
1733
raise errors.UnexpectedSmartServerResponse(response)
1737
self._lock_count -= 1
1738
if not self._lock_count:
1739
self._clear_cached_state()
1740
mode = self._lock_mode
1741
self._lock_mode = None
1742
if self._real_branch is not None:
1743
if (not self._leave_lock and mode == 'w' and
1744
self._repo_lock_token):
1745
# If this RemoteBranch will remove the physical lock
1746
# for the repository, make sure the _real_branch
1747
# doesn't do it first. (Because the _real_branch's
1748
# repository is set to be the RemoteRepository.)
1749
self._real_branch.repository.leave_lock_in_place()
1750
self._real_branch.unlock()
1752
# Only write-locked branched need to make a remote method
1753
# call to perfom the unlock.
1755
if not self._lock_token:
1756
raise AssertionError('Locked, but no token!')
1757
branch_token = self._lock_token
1758
repo_token = self._repo_lock_token
1759
self._lock_token = None
1760
self._repo_lock_token = None
1761
if not self._leave_lock:
1762
self._unlock(branch_token, repo_token)
1764
self.repository.unlock()
1766
def break_lock(self):
1768
return self._real_branch.break_lock()
1770
def leave_lock_in_place(self):
1771
if not self._lock_token:
1772
raise NotImplementedError(self.leave_lock_in_place)
1773
self._leave_lock = True
1775
def dont_leave_lock_in_place(self):
1776
if not self._lock_token:
1777
raise NotImplementedError(self.dont_leave_lock_in_place)
1778
self._leave_lock = False
1780
def _last_revision_info(self):
1781
response = self._call('Branch.last_revision_info', self._remote_path())
1782
if response[0] != 'ok':
1783
raise SmartProtocolError('unexpected response code %s' % (response,))
1784
revno = int(response[1])
1785
last_revision = response[2]
1786
return (revno, last_revision)
1788
def _gen_revision_history(self):
1789
"""See Branch._gen_revision_history()."""
1790
response_tuple, response_handler = self._call_expecting_body(
1791
'Branch.revision_history', self._remote_path())
1792
if response_tuple[0] != 'ok':
1793
raise errors.UnexpectedSmartServerResponse(response_tuple)
1794
result = response_handler.read_body_bytes().split('\x00')
1799
def _remote_path(self):
1800
return self.bzrdir._path_for_remote_call(self._client)
1802
def _set_last_revision_descendant(self, revision_id, other_branch,
1803
allow_diverged=False, allow_overwrite_descendant=False):
1804
# This performs additional work to meet the hook contract; while its
1805
# undesirable, we have to synthesise the revno to call the hook, and
1806
# not calling the hook is worse as it means changes can't be prevented.
1807
# Having calculated this though, we can't just call into
1808
# set_last_revision_info as a simple call, because there is a set_rh
1809
# hook that some folk may still be using.
1810
old_revno, old_revid = self.last_revision_info()
1811
history = self._lefthand_history(revision_id)
1812
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1813
err_context = {'other_branch': other_branch}
1814
response = self._call('Branch.set_last_revision_ex',
1815
self._remote_path(), self._lock_token, self._repo_lock_token,
1816
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1818
self._clear_cached_state()
1819
if len(response) != 3 and response[0] != 'ok':
1820
raise errors.UnexpectedSmartServerResponse(response)
1821
new_revno, new_revision_id = response[1:]
1822
self._last_revision_info_cache = new_revno, new_revision_id
1823
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1824
if self._real_branch is not None:
1825
cache = new_revno, new_revision_id
1826
self._real_branch._last_revision_info_cache = cache
1828
def _set_last_revision(self, revision_id):
1829
old_revno, old_revid = self.last_revision_info()
1830
# This performs additional work to meet the hook contract; while its
1831
# undesirable, we have to synthesise the revno to call the hook, and
1832
# not calling the hook is worse as it means changes can't be prevented.
1833
# Having calculated this though, we can't just call into
1834
# set_last_revision_info as a simple call, because there is a set_rh
1835
# hook that some folk may still be using.
1836
history = self._lefthand_history(revision_id)
1837
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1838
self._clear_cached_state()
1839
response = self._call('Branch.set_last_revision',
1840
self._remote_path(), self._lock_token, self._repo_lock_token,
1842
if response != ('ok',):
1843
raise errors.UnexpectedSmartServerResponse(response)
1844
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1847
def set_revision_history(self, rev_history):
1848
# Send just the tip revision of the history; the server will generate
1849
# the full history from that. If the revision doesn't exist in this
1850
# branch, NoSuchRevision will be raised.
1851
if rev_history == []:
1854
rev_id = rev_history[-1]
1855
self._set_last_revision(rev_id)
1856
for hook in branch.Branch.hooks['set_rh']:
1857
hook(self, rev_history)
1858
self._cache_revision_history(rev_history)
1860
def get_parent(self):
1862
return self._real_branch.get_parent()
1864
def _get_parent_location(self):
1865
# Used by tests, when checking normalisation of given vs stored paths.
1867
return self._real_branch._get_parent_location()
1869
def set_parent(self, url):
1871
return self._real_branch.set_parent(url)
1873
def _set_parent_location(self, url):
1874
# Used by tests, to poke bad urls into branch configurations
1876
self.set_parent(url)
1879
return self._real_branch._set_parent_location(url)
1881
def set_stacked_on_url(self, stacked_location):
1882
"""Set the URL this branch is stacked against.
1884
:raises UnstackableBranchFormat: If the branch does not support
1886
:raises UnstackableRepositoryFormat: If the repository does not support
1890
return self._real_branch.set_stacked_on_url(stacked_location)
1892
def sprout(self, to_bzrdir, revision_id=None):
1893
branch_format = to_bzrdir._format._branch_format
1894
if (branch_format is None or
1895
isinstance(branch_format, RemoteBranchFormat)):
1896
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1897
# implies the same thing), but RemoteBranches can't be created at
1898
# arbitrary URLs. So create a branch in the same format as
1899
# _real_branch instead.
1900
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1901
# to_bzrdir.create_branch to create a RemoteBranch after all...
1903
result = self._real_branch._format.initialize(to_bzrdir)
1904
self.copy_content_into(result, revision_id=revision_id)
1905
result.set_parent(self.bzrdir.root_transport.base)
1907
result = branch.Branch.sprout(
1908
self, to_bzrdir, revision_id=revision_id)
1912
def pull(self, source, overwrite=False, stop_revision=None,
1914
self._clear_cached_state_of_remote_branch_only()
1916
return self._real_branch.pull(
1917
source, overwrite=overwrite, stop_revision=stop_revision,
1918
_override_hook_target=self, **kwargs)
1921
def push(self, target, overwrite=False, stop_revision=None):
1923
return self._real_branch.push(
1924
target, overwrite=overwrite, stop_revision=stop_revision,
1925
_override_hook_source_branch=self)
1927
def is_locked(self):
1928
return self._lock_count >= 1
1931
def revision_id_to_revno(self, revision_id):
1933
return self._real_branch.revision_id_to_revno(revision_id)
1936
def set_last_revision_info(self, revno, revision_id):
1937
# XXX: These should be returned by the set_last_revision_info verb
1938
old_revno, old_revid = self.last_revision_info()
1939
self._run_pre_change_branch_tip_hooks(revno, revision_id)
1940
revision_id = ensure_null(revision_id)
1942
response = self._call('Branch.set_last_revision_info',
1943
self._remote_path(), self._lock_token, self._repo_lock_token,
1944
str(revno), revision_id)
1945
except errors.UnknownSmartMethod:
1947
self._clear_cached_state_of_remote_branch_only()
1948
self._real_branch.set_last_revision_info(revno, revision_id)
1949
self._last_revision_info_cache = revno, revision_id
1951
if response == ('ok',):
1952
self._clear_cached_state()
1953
self._last_revision_info_cache = revno, revision_id
1954
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1955
# Update the _real_branch's cache too.
1956
if self._real_branch is not None:
1957
cache = self._last_revision_info_cache
1958
self._real_branch._last_revision_info_cache = cache
1960
raise errors.UnexpectedSmartServerResponse(response)
1963
def generate_revision_history(self, revision_id, last_rev=None,
1965
medium = self._client._medium
1966
if not medium._is_remote_before((1, 6)):
1967
# Use a smart method for 1.6 and above servers
1969
self._set_last_revision_descendant(revision_id, other_branch,
1970
allow_diverged=True, allow_overwrite_descendant=True)
1972
except errors.UnknownSmartMethod:
1973
medium._remember_remote_is_before((1, 6))
1974
self._clear_cached_state_of_remote_branch_only()
1975
self.set_revision_history(self._lefthand_history(revision_id,
1976
last_rev=last_rev,other_branch=other_branch))
1981
return self._real_branch.tags
1983
def set_push_location(self, location):
1985
return self._real_branch.set_push_location(location)
1988
def update_revisions(self, other, stop_revision=None, overwrite=False,
1990
"""See Branch.update_revisions."""
1993
if stop_revision is None:
1994
stop_revision = other.last_revision()
1995
if revision.is_null(stop_revision):
1996
# if there are no commits, we're done.
1998
self.fetch(other, stop_revision)
2001
# Just unconditionally set the new revision. We don't care if
2002
# the branches have diverged.
2003
self._set_last_revision(stop_revision)
2005
medium = self._client._medium
2006
if not medium._is_remote_before((1, 6)):
2008
self._set_last_revision_descendant(stop_revision, other)
2010
except errors.UnknownSmartMethod:
2011
medium._remember_remote_is_before((1, 6))
2012
# Fallback for pre-1.6 servers: check for divergence
2013
# client-side, then do _set_last_revision.
2014
last_rev = revision.ensure_null(self.last_revision())
2016
graph = self.repository.get_graph()
2017
if self._check_if_descendant_or_diverged(
2018
stop_revision, last_rev, graph, other):
2019
# stop_revision is a descendant of last_rev, but we aren't
2020
# overwriting, so we're done.
2022
self._set_last_revision(stop_revision)
2027
def _extract_tar(tar, to_dir):
2028
"""Extract all the contents of a tarfile object.
2030
A replacement for extractall, which is not present in python2.4
2033
tar.extract(tarinfo, to_dir)
2036
def _translate_error(err, **context):
2037
"""Translate an ErrorFromSmartServer into a more useful error.
2039
Possible context keys:
2047
If the error from the server doesn't match a known pattern, then
2048
UnknownErrorFromSmartServer is raised.
2052
return context[name]
2053
except KeyError, key_err:
2054
mutter('Missing key %r in context %r', key_err.args[0], context)
2057
"""Get the path from the context if present, otherwise use first error
2061
return context['path']
2062
except KeyError, key_err:
2064
return err.error_args[0]
2065
except IndexError, idx_err:
2067
'Missing key %r in context %r', key_err.args[0], context)
2070
if err.error_verb == 'NoSuchRevision':
2071
raise NoSuchRevision(find('branch'), err.error_args[0])
2072
elif err.error_verb == 'nosuchrevision':
2073
raise NoSuchRevision(find('repository'), err.error_args[0])
2074
elif err.error_tuple == ('nobranch',):
2075
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2076
elif err.error_verb == 'norepository':
2077
raise errors.NoRepositoryPresent(find('bzrdir'))
2078
elif err.error_verb == 'LockContention':
2079
raise errors.LockContention('(remote lock)')
2080
elif err.error_verb == 'UnlockableTransport':
2081
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2082
elif err.error_verb == 'LockFailed':
2083
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2084
elif err.error_verb == 'TokenMismatch':
2085
raise errors.TokenMismatch(find('token'), '(remote token)')
2086
elif err.error_verb == 'Diverged':
2087
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2088
elif err.error_verb == 'TipChangeRejected':
2089
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2090
elif err.error_verb == 'UnstackableBranchFormat':
2091
raise errors.UnstackableBranchFormat(*err.error_args)
2092
elif err.error_verb == 'UnstackableRepositoryFormat':
2093
raise errors.UnstackableRepositoryFormat(*err.error_args)
2094
elif err.error_verb == 'NotStacked':
2095
raise errors.NotStacked(branch=find('branch'))
2096
elif err.error_verb == 'PermissionDenied':
2098
if len(err.error_args) >= 2:
2099
extra = err.error_args[1]
2102
raise errors.PermissionDenied(path, extra=extra)
2103
elif err.error_verb == 'ReadError':
2105
raise errors.ReadError(path)
2106
elif err.error_verb == 'NoSuchFile':
2108
raise errors.NoSuchFile(path)
2109
elif err.error_verb == 'FileExists':
2110
raise errors.FileExists(err.error_args[0])
2111
elif err.error_verb == 'DirectoryNotEmpty':
2112
raise errors.DirectoryNotEmpty(err.error_args[0])
2113
elif err.error_verb == 'ShortReadvError':
2114
args = err.error_args
2115
raise errors.ShortReadvError(
2116
args[0], int(args[1]), int(args[2]), int(args[3]))
2117
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2118
encoding = str(err.error_args[0]) # encoding must always be a string
2119
val = err.error_args[1]
2120
start = int(err.error_args[2])
2121
end = int(err.error_args[3])
2122
reason = str(err.error_args[4]) # reason must always be a string
2123
if val.startswith('u:'):
2124
val = val[2:].decode('utf-8')
2125
elif val.startswith('s:'):
2126
val = val[2:].decode('base64')
2127
if err.error_verb == 'UnicodeDecodeError':
2128
raise UnicodeDecodeError(encoding, val, start, end, reason)
2129
elif err.error_verb == 'UnicodeEncodeError':
2130
raise UnicodeEncodeError(encoding, val, start, end, reason)
2131
elif err.error_verb == 'ReadOnlyError':
2132
raise errors.TransportNotPossible('readonly transport')
2133
raise errors.UnknownErrorFromSmartServer(err)