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)
74
def response_tuple_to_repo_format(response):
75
"""Convert a response tuple describing a repository format to a format."""
76
format = RemoteRepositoryFormat()
77
format.rich_root_data = (response[0] == 'yes')
78
format.supports_tree_reference = (response[1] == 'yes')
79
format.supports_external_lookups = (response[2] == 'yes')
80
format._network_name = response[3]
84
# Note: RemoteBzrDirFormat is in bzrdir.py
86
class RemoteBzrDir(BzrDir, _RpcHelper):
87
"""Control directory on a remote server, accessed via bzr:// or similar."""
89
def __init__(self, transport, format, _client=None):
90
"""Construct a RemoteBzrDir.
92
:param _client: Private parameter for testing. Disables probing and the
95
BzrDir.__init__(self, transport, format)
96
# this object holds a delegated bzrdir that uses file-level operations
97
# to talk to the other side
98
self._real_bzrdir = None
101
medium = transport.get_smart_medium()
102
self._client = client._SmartClient(medium)
104
self._client = _client
107
path = self._path_for_remote_call(self._client)
108
response = self._call('BzrDir.open', path)
109
if response not in [('yes',), ('no',)]:
110
raise errors.UnexpectedSmartServerResponse(response)
111
if response == ('no',):
112
raise errors.NotBranchError(path=transport.base)
114
def _ensure_real(self):
115
"""Ensure that there is a _real_bzrdir set.
117
Used before calls to self._real_bzrdir.
119
if not self._real_bzrdir:
120
self._real_bzrdir = BzrDir.open_from_transport(
121
self.root_transport, _server_formats=False)
123
def _translate_error(self, err, **context):
124
_translate_error(err, bzrdir=self, **context)
126
def cloning_metadir(self, stacked=False):
128
return self._real_bzrdir.cloning_metadir(stacked)
130
def create_repository(self, shared=False):
131
# as per meta1 formats - just delegate to the format object which may
133
result = self._format.repository_format.initialize(self, shared)
134
if not isinstance(result, RemoteRepository):
135
return self.open_repository()
139
def destroy_repository(self):
140
"""See BzrDir.destroy_repository"""
142
self._real_bzrdir.destroy_repository()
144
def create_branch(self):
145
# as per meta1 formats - just delegate to the format object which may
147
real_branch = self._format.get_branch_format().initialize(self)
148
if not isinstance(real_branch, RemoteBranch):
149
return RemoteBranch(self, self.find_repository(), real_branch)
153
def destroy_branch(self):
154
"""See BzrDir.destroy_branch"""
156
self._real_bzrdir.destroy_branch()
158
def create_workingtree(self, revision_id=None, from_branch=None):
159
raise errors.NotLocalUrl(self.transport.base)
161
def find_branch_format(self):
162
"""Find the branch 'format' for this bzrdir.
164
This might be a synthetic object for e.g. RemoteBranch and SVN.
166
b = self.open_branch()
169
def get_branch_reference(self):
170
"""See BzrDir.get_branch_reference()."""
171
path = self._path_for_remote_call(self._client)
172
response = self._call('BzrDir.open_branch', path)
173
if response[0] == 'ok':
174
if response[1] == '':
175
# branch at this location.
178
# a branch reference, use the existing BranchReference logic.
181
raise errors.UnexpectedSmartServerResponse(response)
183
def _get_tree_branch(self):
184
"""See BzrDir._get_tree_branch()."""
185
return None, self.open_branch()
187
def open_branch(self, _unsupported=False):
189
raise NotImplementedError('unsupported flag support not implemented yet.')
190
reference_url = self.get_branch_reference()
191
if reference_url is None:
192
# branch at this location.
193
return RemoteBranch(self, self.find_repository())
195
# a branch reference, use the existing BranchReference logic.
196
format = BranchReferenceFormat()
197
return format.open(self, _found=True, location=reference_url)
199
def open_repository(self):
200
path = self._path_for_remote_call(self._client)
201
verb = 'BzrDir.find_repositoryV2'
203
response = self._call(verb, path)
204
except errors.UnknownSmartMethod:
205
verb = 'BzrDir.find_repository'
206
response = self._call(verb, path)
207
if response[0] != 'ok':
208
raise errors.UnexpectedSmartServerResponse(response)
209
if verb == 'BzrDir.find_repository':
210
# servers that don't support the V2 method don't support external
212
response = response + ('no', )
213
if not (len(response) == 5):
214
raise SmartProtocolError('incorrect response length %s' % (response,))
215
if response[1] == '':
216
format = RemoteRepositoryFormat()
217
format.rich_root_data = (response[2] == 'yes')
218
format.supports_tree_reference = (response[3] == 'yes')
219
# No wire format to check this yet.
220
format.supports_external_lookups = (response[4] == 'yes')
221
# Used to support creating a real format instance when needed.
222
format._creating_bzrdir = self
223
remote_repo = RemoteRepository(self, format)
224
format._creating_repo = remote_repo
227
raise errors.NoRepositoryPresent(self)
229
def open_workingtree(self, recommend_upgrade=True):
231
if self._real_bzrdir.has_workingtree():
232
raise errors.NotLocalUrl(self.root_transport)
234
raise errors.NoWorkingTree(self.root_transport.base)
236
def _path_for_remote_call(self, client):
237
"""Return the path to be used for this bzrdir in a remote call."""
238
return client.remote_path_from_transport(self.root_transport)
240
def get_branch_transport(self, branch_format):
242
return self._real_bzrdir.get_branch_transport(branch_format)
244
def get_repository_transport(self, repository_format):
246
return self._real_bzrdir.get_repository_transport(repository_format)
248
def get_workingtree_transport(self, workingtree_format):
250
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
252
def can_convert_format(self):
253
"""Upgrading of remote bzrdirs is not supported yet."""
256
def needs_format_conversion(self, format=None):
257
"""Upgrading of remote bzrdirs is not supported yet."""
259
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
260
% 'needs_format_conversion(format=None)')
263
def clone(self, url, revision_id=None, force_new_repo=False,
264
preserve_stacking=False):
266
return self._real_bzrdir.clone(url, revision_id=revision_id,
267
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
269
def get_config(self):
271
return self._real_bzrdir.get_config()
274
class RemoteRepositoryFormat(repository.RepositoryFormat):
275
"""Format for repositories accessed over a _SmartClient.
277
Instances of this repository are represented by RemoteRepository
280
The RemoteRepositoryFormat is parameterized during construction
281
to reflect the capabilities of the real, remote format. Specifically
282
the attributes rich_root_data and supports_tree_reference are set
283
on a per instance basis, and are not set (and should not be) at
286
:ivar _custom_format: If set, a specific concrete repository format that
287
will be used when initializing a repository with this
288
RemoteRepositoryFormat.
289
:ivar _creating_repo: If set, the repository object that this
290
RemoteRepositoryFormat was created for: it can be called into
291
to obtain data like the network name.
294
_matchingbzrdir = RemoteBzrDirFormat()
297
repository.RepositoryFormat.__init__(self)
298
self._custom_format = None
299
self._network_name = None
300
self._creating_bzrdir = None
302
def _vfs_initialize(self, a_bzrdir, shared):
303
"""Helper for common code in initialize."""
304
if self._custom_format:
305
# Custom format requested
306
result = self._custom_format.initialize(a_bzrdir, shared=shared)
307
elif self._creating_bzrdir is not None:
308
# Use the format that the repository we were created to back
310
prior_repo = self._creating_bzrdir.open_repository()
311
prior_repo._ensure_real()
312
result = prior_repo._real_repository._format.initialize(
313
a_bzrdir, shared=shared)
315
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
316
# support remote initialization.
317
# We delegate to a real object at this point (as RemoteBzrDir
318
# delegate to the repository format which would lead to infinite
319
# recursion if we just called a_bzrdir.create_repository.
320
a_bzrdir._ensure_real()
321
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
322
if not isinstance(result, RemoteRepository):
323
return self.open(a_bzrdir)
327
def initialize(self, a_bzrdir, shared=False):
328
# Being asked to create on a non RemoteBzrDir:
329
if not isinstance(a_bzrdir, RemoteBzrDir):
330
return self._vfs_initialize(a_bzrdir, shared)
331
medium = a_bzrdir._client._medium
332
if medium._is_remote_before((1, 13)):
333
return self._vfs_initialize(a_bzrdir, shared)
334
# Creating on a remote bzr dir.
335
# 1) get the network name to use.
336
if self._custom_format:
337
network_name = self._custom_format.network_name()
339
# Select the current bzrlib default and ask for that.
340
reference_bzrdir_format = bzrdir.format_registry.get('default')()
341
reference_format = reference_bzrdir_format.repository_format
342
network_name = reference_format.network_name()
343
# 2) try direct creation via RPC
344
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
345
verb = 'BzrDir.create_repository'
351
response = a_bzrdir._call(verb, path, network_name, shared_str)
352
except errors.UnknownSmartMethod:
353
# Fallback - use vfs methods
354
return self._vfs_initialize(a_bzrdir, shared)
356
# Turn the response into a RemoteRepository object.
357
format = response_tuple_to_repo_format(response[1:])
358
# Used to support creating a real format instance when needed.
359
format._creating_bzrdir = a_bzrdir
360
remote_repo = RemoteRepository(a_bzrdir, format)
361
format._creating_repo = remote_repo
364
def open(self, a_bzrdir):
365
if not isinstance(a_bzrdir, RemoteBzrDir):
366
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
367
return a_bzrdir.open_repository()
369
def get_format_description(self):
370
return 'bzr remote repository'
372
def __eq__(self, other):
373
return self.__class__ == other.__class__
375
def check_conversion_target(self, target_format):
376
if self.rich_root_data and not target_format.rich_root_data:
377
raise errors.BadConversionTarget(
378
'Does not support rich root data.', target_format)
379
if (self.supports_tree_reference and
380
not getattr(target_format, 'supports_tree_reference', False)):
381
raise errors.BadConversionTarget(
382
'Does not support nested trees', target_format)
384
def network_name(self):
385
if self._network_name:
386
return self._network_name
387
self._creating_repo._ensure_real()
388
return self._creating_repo._real_repository._format.network_name()
391
def _serializer(self):
392
if self._custom_format is not None:
393
return self._custom_format._serializer
394
elif self._network_name is not None:
395
self._custom_format = repository.network_format_registry.get(
397
return self._custom_format._serializer
399
# We should only be getting asked for the serializer for
400
# RemoteRepositoryFormat objects when the RemoteRepositoryFormat object
401
# is a concrete instance for a RemoteRepository. In this case we know
402
# the creating_repo and can use it to supply the serializer.
403
self._creating_repo._ensure_real()
404
return self._creating_repo._real_repository._format._serializer
407
class RemoteRepository(_RpcHelper):
408
"""Repository accessed over rpc.
410
For the moment most operations are performed using local transport-backed
414
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
415
"""Create a RemoteRepository instance.
417
:param remote_bzrdir: The bzrdir hosting this repository.
418
:param format: The RemoteFormat object to use.
419
:param real_repository: If not None, a local implementation of the
420
repository logic for the repository, usually accessing the data
422
:param _client: Private testing parameter - override the smart client
423
to be used by the repository.
426
self._real_repository = real_repository
428
self._real_repository = None
429
self.bzrdir = remote_bzrdir
431
self._client = remote_bzrdir._client
433
self._client = _client
434
self._format = format
435
self._lock_mode = None
436
self._lock_token = None
438
self._leave_lock = False
439
self._unstacked_provider = graph.CachingParentsProvider(
440
get_parent_map=self._get_parent_map_rpc)
441
self._unstacked_provider.disable_cache()
443
# These depend on the actual remote format, so force them off for
444
# maximum compatibility. XXX: In future these should depend on the
445
# remote repository instance, but this is irrelevant until we perform
446
# reconcile via an RPC call.
447
self._reconcile_does_inventory_gc = False
448
self._reconcile_fixes_text_parents = False
449
self._reconcile_backsup_inventory = False
450
self.base = self.bzrdir.transport.base
451
# Additional places to query for data.
452
self._fallback_repositories = []
455
return "%s(%s)" % (self.__class__.__name__, self.base)
459
def abort_write_group(self, suppress_errors=False):
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
467
:param suppress_errors: see Repository.abort_write_group.
470
return self._real_repository.abort_write_group(
471
suppress_errors=suppress_errors)
473
def commit_write_group(self):
474
"""Complete a write group on the decorated repository.
476
Smart methods peform operations in a single step so this api
477
is not really applicable except as a compatibility thunk
478
for older plugins that don't use e.g. the CommitBuilder
482
return self._real_repository.commit_write_group()
484
def resume_write_group(self, tokens):
486
return self._real_repository.resume_write_group(tokens)
488
def suspend_write_group(self):
490
return self._real_repository.suspend_write_group()
492
def _ensure_real(self):
493
"""Ensure that there is a _real_repository set.
495
Used before calls to self._real_repository.
497
if self._real_repository is None:
498
self.bzrdir._ensure_real()
499
self._set_real_repository(
500
self.bzrdir._real_bzrdir.open_repository())
502
def _translate_error(self, err, **context):
503
self.bzrdir._translate_error(err, repository=self, **context)
505
def find_text_key_references(self):
506
"""Find the text key references within the repository.
508
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
509
revision_ids. Each altered file-ids has the exact revision_ids that
510
altered it listed explicitly.
511
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
512
to whether they were referred to by the inventory of the
513
revision_id that they contain. The inventory texts from all present
514
revision ids are assessed to generate this report.
517
return self._real_repository.find_text_key_references()
519
def _generate_text_key_index(self):
520
"""Generate a new text key index for the repository.
522
This is an expensive function that will take considerable time to run.
524
:return: A dict mapping (file_id, revision_id) tuples to a list of
525
parents, also (file_id, revision_id) tuples.
528
return self._real_repository._generate_text_key_index()
530
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
531
def get_revision_graph(self, revision_id=None):
532
"""See Repository.get_revision_graph()."""
533
return self._get_revision_graph(revision_id)
535
def _get_revision_graph(self, revision_id):
536
"""Private method for using with old (< 1.2) servers to fallback."""
537
if revision_id is None:
539
elif revision.is_null(revision_id):
542
path = self.bzrdir._path_for_remote_call(self._client)
543
response = self._call_expecting_body(
544
'Repository.get_revision_graph', path, revision_id)
545
response_tuple, response_handler = response
546
if response_tuple[0] != 'ok':
547
raise errors.UnexpectedSmartServerResponse(response_tuple)
548
coded = response_handler.read_body_bytes()
550
# no revisions in this repository!
552
lines = coded.split('\n')
555
d = tuple(line.split())
556
revision_graph[d[0]] = d[1:]
558
return revision_graph
561
"""See Repository._get_sink()."""
562
return RemoteStreamSink(self)
564
def has_revision(self, revision_id):
565
"""See Repository.has_revision()."""
566
if revision_id == NULL_REVISION:
567
# The null revision is always present.
569
path = self.bzrdir._path_for_remote_call(self._client)
570
response = self._call('Repository.has_revision', path, revision_id)
571
if response[0] not in ('yes', 'no'):
572
raise errors.UnexpectedSmartServerResponse(response)
573
if response[0] == 'yes':
575
for fallback_repo in self._fallback_repositories:
576
if fallback_repo.has_revision(revision_id):
580
def has_revisions(self, revision_ids):
581
"""See Repository.has_revisions()."""
582
# FIXME: This does many roundtrips, particularly when there are
583
# fallback repositories. -- mbp 20080905
585
for revision_id in revision_ids:
586
if self.has_revision(revision_id):
587
result.add(revision_id)
590
def has_same_location(self, other):
591
return (self.__class__ == other.__class__ and
592
self.bzrdir.transport.base == other.bzrdir.transport.base)
594
def get_graph(self, other_repository=None):
595
"""Return the graph for this repository format"""
596
parents_provider = self._make_parents_provider(other_repository)
597
return graph.Graph(parents_provider)
599
def gather_stats(self, revid=None, committers=None):
600
"""See Repository.gather_stats()."""
601
path = self.bzrdir._path_for_remote_call(self._client)
602
# revid can be None to indicate no revisions, not just NULL_REVISION
603
if revid is None or revision.is_null(revid):
607
if committers is None or not committers:
608
fmt_committers = 'no'
610
fmt_committers = 'yes'
611
response_tuple, response_handler = self._call_expecting_body(
612
'Repository.gather_stats', path, fmt_revid, fmt_committers)
613
if response_tuple[0] != 'ok':
614
raise errors.UnexpectedSmartServerResponse(response_tuple)
616
body = response_handler.read_body_bytes()
618
for line in body.split('\n'):
621
key, val_text = line.split(':')
622
if key in ('revisions', 'size', 'committers'):
623
result[key] = int(val_text)
624
elif key in ('firstrev', 'latestrev'):
625
values = val_text.split(' ')[1:]
626
result[key] = (float(values[0]), long(values[1]))
630
def find_branches(self, using=False):
631
"""See Repository.find_branches()."""
632
# should be an API call to the server.
634
return self._real_repository.find_branches(using=using)
636
def get_physical_lock_status(self):
637
"""See Repository.get_physical_lock_status()."""
638
# should be an API call to the server.
640
return self._real_repository.get_physical_lock_status()
642
def is_in_write_group(self):
643
"""Return True if there is an open write group.
645
write groups are only applicable locally for the smart server..
647
if self._real_repository:
648
return self._real_repository.is_in_write_group()
651
return self._lock_count >= 1
654
"""See Repository.is_shared()."""
655
path = self.bzrdir._path_for_remote_call(self._client)
656
response = self._call('Repository.is_shared', path)
657
if response[0] not in ('yes', 'no'):
658
raise SmartProtocolError('unexpected response code %s' % (response,))
659
return response[0] == 'yes'
661
def is_write_locked(self):
662
return self._lock_mode == 'w'
665
# wrong eventually - want a local lock cache context
666
if not self._lock_mode:
667
self._lock_mode = 'r'
669
self._unstacked_provider.enable_cache(cache_misses=False)
670
if self._real_repository is not None:
671
self._real_repository.lock_read()
673
self._lock_count += 1
675
def _remote_lock_write(self, token):
676
path = self.bzrdir._path_for_remote_call(self._client)
679
err_context = {'token': token}
680
response = self._call('Repository.lock_write', path, token,
682
if response[0] == 'ok':
686
raise errors.UnexpectedSmartServerResponse(response)
688
def lock_write(self, token=None, _skip_rpc=False):
689
if not self._lock_mode:
691
if self._lock_token is not None:
692
if token != self._lock_token:
693
raise errors.TokenMismatch(token, self._lock_token)
694
self._lock_token = token
696
self._lock_token = self._remote_lock_write(token)
697
# if self._lock_token is None, then this is something like packs or
698
# svn where we don't get to lock the repo, or a weave style repository
699
# where we cannot lock it over the wire and attempts to do so will
701
if self._real_repository is not None:
702
self._real_repository.lock_write(token=self._lock_token)
703
if token is not None:
704
self._leave_lock = True
706
self._leave_lock = False
707
self._lock_mode = 'w'
709
self._unstacked_provider.enable_cache(cache_misses=False)
710
elif self._lock_mode == 'r':
711
raise errors.ReadOnlyError(self)
713
self._lock_count += 1
714
return self._lock_token or None
716
def leave_lock_in_place(self):
717
if not self._lock_token:
718
raise NotImplementedError(self.leave_lock_in_place)
719
self._leave_lock = True
721
def dont_leave_lock_in_place(self):
722
if not self._lock_token:
723
raise NotImplementedError(self.dont_leave_lock_in_place)
724
self._leave_lock = False
726
def _set_real_repository(self, repository):
727
"""Set the _real_repository for this repository.
729
:param repository: The repository to fallback to for non-hpss
730
implemented operations.
732
if self._real_repository is not None:
733
raise AssertionError('_real_repository is already set')
734
if isinstance(repository, RemoteRepository):
735
raise AssertionError()
736
self._real_repository = repository
737
for fb in self._fallback_repositories:
738
self._real_repository.add_fallback_repository(fb)
739
if self._lock_mode == 'w':
740
# if we are already locked, the real repository must be able to
741
# acquire the lock with our token.
742
self._real_repository.lock_write(self._lock_token)
743
elif self._lock_mode == 'r':
744
self._real_repository.lock_read()
746
def start_write_group(self):
747
"""Start a write group on the decorated repository.
749
Smart methods peform operations in a single step so this api
750
is not really applicable except as a compatibility thunk
751
for older plugins that don't use e.g. the CommitBuilder
755
return self._real_repository.start_write_group()
757
def _unlock(self, token):
758
path = self.bzrdir._path_for_remote_call(self._client)
760
# with no token the remote repository is not persistently locked.
762
err_context = {'token': token}
763
response = self._call('Repository.unlock', path, token,
765
if response == ('ok',):
768
raise errors.UnexpectedSmartServerResponse(response)
771
if not self._lock_count:
772
raise errors.LockNotHeld(self)
773
self._lock_count -= 1
774
if self._lock_count > 0:
776
self._unstacked_provider.disable_cache()
777
old_mode = self._lock_mode
778
self._lock_mode = None
780
# The real repository is responsible at present for raising an
781
# exception if it's in an unfinished write group. However, it
782
# normally will *not* actually remove the lock from disk - that's
783
# done by the server on receiving the Repository.unlock call.
784
# This is just to let the _real_repository stay up to date.
785
if self._real_repository is not None:
786
self._real_repository.unlock()
788
# The rpc-level lock should be released even if there was a
789
# problem releasing the vfs-based lock.
791
# Only write-locked repositories need to make a remote method
792
# call to perfom the unlock.
793
old_token = self._lock_token
794
self._lock_token = None
795
if not self._leave_lock:
796
self._unlock(old_token)
798
def break_lock(self):
799
# should hand off to the network
801
return self._real_repository.break_lock()
803
def _get_tarball(self, compression):
804
"""Return a TemporaryFile containing a repository tarball.
806
Returns None if the server does not support sending tarballs.
809
path = self.bzrdir._path_for_remote_call(self._client)
811
response, protocol = self._call_expecting_body(
812
'Repository.tarball', path, compression)
813
except errors.UnknownSmartMethod:
814
protocol.cancel_read_body()
816
if response[0] == 'ok':
817
# Extract the tarball and return it
818
t = tempfile.NamedTemporaryFile()
819
# TODO: rpc layer should read directly into it...
820
t.write(protocol.read_body_bytes())
823
raise errors.UnexpectedSmartServerResponse(response)
825
def sprout(self, to_bzrdir, revision_id=None):
826
# TODO: Option to control what format is created?
828
dest_repo = self._real_repository._format.initialize(to_bzrdir,
830
dest_repo.fetch(self, revision_id=revision_id)
833
### These methods are just thin shims to the VFS object for now.
835
def revision_tree(self, revision_id):
837
return self._real_repository.revision_tree(revision_id)
839
def get_serializer_format(self):
841
return self._real_repository.get_serializer_format()
843
def get_commit_builder(self, branch, parents, config, timestamp=None,
844
timezone=None, committer=None, revprops=None,
846
# FIXME: It ought to be possible to call this without immediately
847
# triggering _ensure_real. For now it's the easiest thing to do.
849
real_repo = self._real_repository
850
builder = real_repo.get_commit_builder(branch, parents,
851
config, timestamp=timestamp, timezone=timezone,
852
committer=committer, revprops=revprops, revision_id=revision_id)
855
def add_fallback_repository(self, repository):
856
"""Add a repository to use for looking up data not held locally.
858
:param repository: A repository.
860
# XXX: At the moment the RemoteRepository will allow fallbacks
861
# unconditionally - however, a _real_repository will usually exist,
862
# and may raise an error if it's not accommodated by the underlying
863
# format. Eventually we should check when opening the repository
864
# whether it's willing to allow them or not.
866
# We need to accumulate additional repositories here, to pass them in
868
self._fallback_repositories.append(repository)
869
# They are also seen by the fallback repository. If it doesn't exist
870
# yet they'll be added then. This implicitly copies them.
873
def add_inventory(self, revid, inv, parents):
875
return self._real_repository.add_inventory(revid, inv, parents)
877
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
880
return self._real_repository.add_inventory_by_delta(basis_revision_id,
881
delta, new_revision_id, parents)
883
def add_revision(self, rev_id, rev, inv=None, config=None):
885
return self._real_repository.add_revision(
886
rev_id, rev, inv=inv, config=config)
889
def get_inventory(self, revision_id):
891
return self._real_repository.get_inventory(revision_id)
893
def iter_inventories(self, revision_ids):
895
return self._real_repository.iter_inventories(revision_ids)
898
def get_revision(self, revision_id):
900
return self._real_repository.get_revision(revision_id)
902
def get_transaction(self):
904
return self._real_repository.get_transaction()
907
def clone(self, a_bzrdir, revision_id=None):
909
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
911
def make_working_trees(self):
912
"""See Repository.make_working_trees"""
914
return self._real_repository.make_working_trees()
916
def revision_ids_to_search_result(self, result_set):
917
"""Convert a set of revision ids to a graph SearchResult."""
918
result_parents = set()
919
for parents in self.get_graph().get_parent_map(
920
result_set).itervalues():
921
result_parents.update(parents)
922
included_keys = result_set.intersection(result_parents)
923
start_keys = result_set.difference(included_keys)
924
exclude_keys = result_parents.difference(result_set)
925
result = graph.SearchResult(start_keys, exclude_keys,
926
len(result_set), result_set)
930
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
931
"""Return the revision ids that other has that this does not.
933
These are returned in topological order.
935
revision_id: only return revision ids included by revision_id.
937
return repository.InterRepository.get(
938
other, self).search_missing_revision_ids(revision_id, find_ghosts)
940
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
941
# Not delegated to _real_repository so that InterRepository.get has a
942
# chance to find an InterRepository specialised for RemoteRepository.
943
if self.has_same_location(source):
944
# check that last_revision is in 'from' and then return a
946
if (revision_id is not None and
947
not revision.is_null(revision_id)):
948
self.get_revision(revision_id)
950
inter = repository.InterRepository.get(source, self)
952
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
953
except NotImplementedError:
954
raise errors.IncompatibleRepositories(source, self)
956
def create_bundle(self, target, base, fileobj, format=None):
958
self._real_repository.create_bundle(target, base, fileobj, format)
961
def get_ancestry(self, revision_id, topo_sorted=True):
963
return self._real_repository.get_ancestry(revision_id, topo_sorted)
965
def fileids_altered_by_revision_ids(self, revision_ids):
967
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
969
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
971
return self._real_repository._get_versioned_file_checker(
972
revisions, revision_versions_cache)
974
def iter_files_bytes(self, desired_files):
975
"""See Repository.iter_file_bytes.
978
return self._real_repository.iter_files_bytes(desired_files)
981
def _fetch_order(self):
982
"""Decorate the real repository for now.
984
In the long term getting this back from the remote repository as part
985
of open would be more efficient.
988
return self._real_repository._fetch_order
991
def _fetch_uses_deltas(self):
992
"""Decorate the real repository for now.
994
In the long term getting this back from the remote repository as part
995
of open would be more efficient.
998
return self._real_repository._fetch_uses_deltas
1001
def _fetch_reconcile(self):
1002
"""Decorate the real repository for now.
1004
In the long term getting this back from the remote repository as part
1005
of open would be more efficient.
1008
return self._real_repository._fetch_reconcile
1010
def get_parent_map(self, revision_ids):
1011
"""See bzrlib.Graph.get_parent_map()."""
1012
return self._make_parents_provider().get_parent_map(revision_ids)
1014
def _get_parent_map_rpc(self, keys):
1015
"""Helper for get_parent_map that performs the RPC."""
1016
medium = self._client._medium
1017
if medium._is_remote_before((1, 2)):
1018
# We already found out that the server can't understand
1019
# Repository.get_parent_map requests, so just fetch the whole
1021
# XXX: Note that this will issue a deprecation warning. This is ok
1022
# :- its because we're working with a deprecated server anyway, and
1023
# the user will almost certainly have seen a warning about the
1024
# server version already.
1025
rg = self.get_revision_graph()
1026
# There is an api discrepency between get_parent_map and
1027
# get_revision_graph. Specifically, a "key:()" pair in
1028
# get_revision_graph just means a node has no parents. For
1029
# "get_parent_map" it means the node is a ghost. So fix up the
1030
# graph to correct this.
1031
# https://bugs.launchpad.net/bzr/+bug/214894
1032
# There is one other "bug" which is that ghosts in
1033
# get_revision_graph() are not returned at all. But we won't worry
1034
# about that for now.
1035
for node_id, parent_ids in rg.iteritems():
1036
if parent_ids == ():
1037
rg[node_id] = (NULL_REVISION,)
1038
rg[NULL_REVISION] = ()
1043
raise ValueError('get_parent_map(None) is not valid')
1044
if NULL_REVISION in keys:
1045
keys.discard(NULL_REVISION)
1046
found_parents = {NULL_REVISION:()}
1048
return found_parents
1051
# TODO(Needs analysis): We could assume that the keys being requested
1052
# from get_parent_map are in a breadth first search, so typically they
1053
# will all be depth N from some common parent, and we don't have to
1054
# have the server iterate from the root parent, but rather from the
1055
# keys we're searching; and just tell the server the keyspace we
1056
# already have; but this may be more traffic again.
1058
# Transform self._parents_map into a search request recipe.
1059
# TODO: Manage this incrementally to avoid covering the same path
1060
# repeatedly. (The server will have to on each request, but the less
1061
# work done the better).
1062
parents_map = self._unstacked_provider.get_cached_map()
1063
if parents_map is None:
1064
# Repository is not locked, so there's no cache.
1066
start_set = set(parents_map)
1067
result_parents = set()
1068
for parents in parents_map.itervalues():
1069
result_parents.update(parents)
1070
stop_keys = result_parents.difference(start_set)
1071
included_keys = start_set.intersection(result_parents)
1072
start_set.difference_update(included_keys)
1073
recipe = (start_set, stop_keys, len(parents_map))
1074
body = self._serialise_search_recipe(recipe)
1075
path = self.bzrdir._path_for_remote_call(self._client)
1077
if type(key) is not str:
1079
"key %r not a plain string" % (key,))
1080
verb = 'Repository.get_parent_map'
1081
args = (path,) + tuple(keys)
1083
response = self._call_with_body_bytes_expecting_body(
1085
except errors.UnknownSmartMethod:
1086
# Server does not support this method, so get the whole graph.
1087
# Worse, we have to force a disconnection, because the server now
1088
# doesn't realise it has a body on the wire to consume, so the
1089
# only way to recover is to abandon the connection.
1091
'Server is too old for fast get_parent_map, reconnecting. '
1092
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1094
# To avoid having to disconnect repeatedly, we keep track of the
1095
# fact the server doesn't understand remote methods added in 1.2.
1096
medium._remember_remote_is_before((1, 2))
1097
return self.get_revision_graph(None)
1098
response_tuple, response_handler = response
1099
if response_tuple[0] not in ['ok']:
1100
response_handler.cancel_read_body()
1101
raise errors.UnexpectedSmartServerResponse(response_tuple)
1102
if response_tuple[0] == 'ok':
1103
coded = bz2.decompress(response_handler.read_body_bytes())
1105
# no revisions found
1107
lines = coded.split('\n')
1110
d = tuple(line.split())
1112
revision_graph[d[0]] = d[1:]
1114
# No parents - so give the Graph result (NULL_REVISION,).
1115
revision_graph[d[0]] = (NULL_REVISION,)
1116
return revision_graph
1119
def get_signature_text(self, revision_id):
1121
return self._real_repository.get_signature_text(revision_id)
1124
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1125
def get_revision_graph_with_ghosts(self, revision_ids=None):
1127
return self._real_repository.get_revision_graph_with_ghosts(
1128
revision_ids=revision_ids)
1131
def get_inventory_xml(self, revision_id):
1133
return self._real_repository.get_inventory_xml(revision_id)
1135
def deserialise_inventory(self, revision_id, xml):
1137
return self._real_repository.deserialise_inventory(revision_id, xml)
1139
def reconcile(self, other=None, thorough=False):
1141
return self._real_repository.reconcile(other=other, thorough=thorough)
1143
def all_revision_ids(self):
1145
return self._real_repository.all_revision_ids()
1148
def get_deltas_for_revisions(self, revisions):
1150
return self._real_repository.get_deltas_for_revisions(revisions)
1153
def get_revision_delta(self, revision_id):
1155
return self._real_repository.get_revision_delta(revision_id)
1158
def revision_trees(self, revision_ids):
1160
return self._real_repository.revision_trees(revision_ids)
1163
def get_revision_reconcile(self, revision_id):
1165
return self._real_repository.get_revision_reconcile(revision_id)
1168
def check(self, revision_ids=None):
1170
return self._real_repository.check(revision_ids=revision_ids)
1172
def copy_content_into(self, destination, revision_id=None):
1174
return self._real_repository.copy_content_into(
1175
destination, revision_id=revision_id)
1177
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1178
# get a tarball of the remote repository, and copy from that into the
1180
from bzrlib import osutils
1182
# TODO: Maybe a progress bar while streaming the tarball?
1183
note("Copying repository content as tarball...")
1184
tar_file = self._get_tarball('bz2')
1185
if tar_file is None:
1187
destination = to_bzrdir.create_repository()
1189
tar = tarfile.open('repository', fileobj=tar_file,
1191
tmpdir = osutils.mkdtemp()
1193
_extract_tar(tar, tmpdir)
1194
tmp_bzrdir = BzrDir.open(tmpdir)
1195
tmp_repo = tmp_bzrdir.open_repository()
1196
tmp_repo.copy_content_into(destination, revision_id)
1198
osutils.rmtree(tmpdir)
1202
# TODO: Suggestion from john: using external tar is much faster than
1203
# python's tarfile library, but it may not work on windows.
1206
def inventories(self):
1207
"""Decorate the real repository for now.
1209
In the long term a full blown network facility is needed to
1210
avoid creating a real repository object locally.
1213
return self._real_repository.inventories
1217
"""Compress the data within the repository.
1219
This is not currently implemented within the smart server.
1222
return self._real_repository.pack()
1225
def revisions(self):
1226
"""Decorate the real repository for now.
1228
In the short term this should become a real object to intercept graph
1231
In the long term a full blown network facility is needed.
1234
return self._real_repository.revisions
1236
def set_make_working_trees(self, new_value):
1238
new_value_str = "True"
1240
new_value_str = "False"
1241
path = self.bzrdir._path_for_remote_call(self._client)
1243
response = self._call(
1244
'Repository.set_make_working_trees', path, new_value_str)
1245
except errors.UnknownSmartMethod:
1247
self._real_repository.set_make_working_trees(new_value)
1249
if response[0] != 'ok':
1250
raise errors.UnexpectedSmartServerResponse(response)
1253
def signatures(self):
1254
"""Decorate the real repository for now.
1256
In the long term a full blown network facility is needed to avoid
1257
creating a real repository object locally.
1260
return self._real_repository.signatures
1263
def sign_revision(self, revision_id, gpg_strategy):
1265
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1269
"""Decorate the real repository for now.
1271
In the long term a full blown network facility is needed to avoid
1272
creating a real repository object locally.
1275
return self._real_repository.texts
1278
def get_revisions(self, revision_ids):
1280
return self._real_repository.get_revisions(revision_ids)
1282
def supports_rich_root(self):
1284
return self._real_repository.supports_rich_root()
1286
def iter_reverse_revision_history(self, revision_id):
1288
return self._real_repository.iter_reverse_revision_history(revision_id)
1291
def _serializer(self):
1293
return self._real_repository._serializer
1295
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1297
return self._real_repository.store_revision_signature(
1298
gpg_strategy, plaintext, revision_id)
1300
def add_signature_text(self, revision_id, signature):
1302
return self._real_repository.add_signature_text(revision_id, signature)
1304
def has_signature_for_revision_id(self, revision_id):
1306
return self._real_repository.has_signature_for_revision_id(revision_id)
1308
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1310
return self._real_repository.item_keys_introduced_by(revision_ids,
1311
_files_pb=_files_pb)
1313
def revision_graph_can_have_wrong_parents(self):
1314
# The answer depends on the remote repo format.
1316
return self._real_repository.revision_graph_can_have_wrong_parents()
1318
def _find_inconsistent_revision_parents(self):
1320
return self._real_repository._find_inconsistent_revision_parents()
1322
def _check_for_inconsistent_revision_parents(self):
1324
return self._real_repository._check_for_inconsistent_revision_parents()
1326
def _make_parents_provider(self, other=None):
1327
providers = [self._unstacked_provider]
1328
if other is not None:
1329
providers.insert(0, other)
1330
providers.extend(r._make_parents_provider() for r in
1331
self._fallback_repositories)
1332
return graph._StackedParentsProvider(providers)
1334
def _serialise_search_recipe(self, recipe):
1335
"""Serialise a graph search recipe.
1337
:param recipe: A search recipe (start, stop, count).
1338
:return: Serialised bytes.
1340
start_keys = ' '.join(recipe[0])
1341
stop_keys = ' '.join(recipe[1])
1342
count = str(recipe[2])
1343
return '\n'.join((start_keys, stop_keys, count))
1346
path = self.bzrdir._path_for_remote_call(self._client)
1348
response = self._call('PackRepository.autopack', path)
1349
except errors.UnknownSmartMethod:
1351
self._real_repository._pack_collection.autopack()
1353
if self._real_repository is not None:
1354
# Reset the real repository's cache of pack names.
1355
# XXX: At some point we may be able to skip this and just rely on
1356
# the automatic retry logic to do the right thing, but for now we
1357
# err on the side of being correct rather than being optimal.
1358
self._real_repository._pack_collection.reload_pack_names()
1359
if response[0] != 'ok':
1360
raise errors.UnexpectedSmartServerResponse(response)
1363
class RemoteStreamSink(repository.StreamSink):
1365
def __init__(self, target_repo):
1366
repository.StreamSink.__init__(self, target_repo)
1367
self._resume_tokens = []
1369
def _insert_real(self, stream, src_format):
1370
self.target_repo._ensure_real()
1371
sink = self.target_repo._real_repository._get_sink()
1372
result = sink.insert_stream(stream, src_format)
1374
self.target_repo.autopack()
1377
def insert_stream(self, stream, src_format):
1378
repo = self.target_repo
1379
client = repo._client
1380
medium = client._medium
1381
if medium._is_remote_before((1, 13)):
1382
# No possible way this can work.
1383
return self._insert_real(stream, src_format)
1384
path = repo.bzrdir._path_for_remote_call(client)
1385
if not self._resume_tokens:
1386
# XXX: Ugly but important for correctness, *will* be fixed during
1387
# 1.13 cycle. Pushing a stream that is interrupted results in a
1388
# fallback to the _real_repositories sink *with a partial stream*.
1389
# Thats bad because we insert less data than bzr expected. To avoid
1390
# this we do a trial push to make sure the verb is accessible, and
1391
# do not fallback when actually pushing the stream. A cleanup patch
1392
# is going to look at rewinding/restarting the stream/partial
1394
byte_stream = self._stream_to_byte_stream([], src_format)
1397
response = client.call_with_body_stream(
1398
('Repository.insert_stream', path, resume_tokens), byte_stream)
1399
except errors.UnknownSmartMethod:
1400
medium._remember_remote_is_before((1,13))
1401
return self._insert_real(stream, src_format)
1402
byte_stream = self._stream_to_byte_stream(stream, src_format)
1403
resume_tokens = ' '.join(self._resume_tokens)
1404
response = client.call_with_body_stream(
1405
('Repository.insert_stream', path, resume_tokens), byte_stream)
1406
if response[0][0] not in ('ok', 'missing-basis'):
1407
raise errors.UnexpectedSmartServerResponse(response)
1408
if response[0][0] == 'missing-basis':
1409
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1410
self._resume_tokens = tokens
1413
if self.target_repo._real_repository is not None:
1414
collection = getattr(self.target_repo._real_repository,
1415
'_pack_collection', None)
1416
if collection is not None:
1417
collection.reload_pack_names()
1420
def _stream_to_byte_stream(self, stream, src_format):
1422
pack_writer = pack.ContainerWriter(bytes.append)
1424
pack_writer.add_bytes_record(src_format.network_name(), '')
1426
def get_adapter(adapter_key):
1428
return adapters[adapter_key]
1430
adapter_factory = adapter_registry.get(adapter_key)
1431
adapter = adapter_factory(self)
1432
adapters[adapter_key] = adapter
1434
for substream_type, substream in stream:
1435
for record in substream:
1436
if record.storage_kind in ('chunked', 'fulltext'):
1437
serialised = record_to_fulltext_bytes(record)
1439
serialised = record.get_bytes_as(record.storage_kind)
1440
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1449
class RemoteBranchLockableFiles(LockableFiles):
1450
"""A 'LockableFiles' implementation that talks to a smart server.
1452
This is not a public interface class.
1455
def __init__(self, bzrdir, _client):
1456
self.bzrdir = bzrdir
1457
self._client = _client
1458
self._need_find_modes = True
1459
LockableFiles.__init__(
1460
self, bzrdir.get_branch_transport(None),
1461
'lock', lockdir.LockDir)
1463
def _find_modes(self):
1464
# RemoteBranches don't let the client set the mode of control files.
1465
self._dir_mode = None
1466
self._file_mode = None
1469
class RemoteBranchFormat(branch.BranchFormat):
1472
super(RemoteBranchFormat, self).__init__()
1473
self._matchingbzrdir = RemoteBzrDirFormat()
1474
self._matchingbzrdir.set_branch_format(self)
1475
self._custom_format = None
1477
def __eq__(self, other):
1478
return (isinstance(other, RemoteBranchFormat) and
1479
self.__dict__ == other.__dict__)
1481
def get_format_description(self):
1482
return 'Remote BZR Branch'
1484
def network_name(self):
1485
return self._network_name
1487
def open(self, a_bzrdir):
1488
return a_bzrdir.open_branch()
1490
def _vfs_initialize(self, a_bzrdir):
1491
# Initialisation when using a local bzrdir object, or a non-vfs init
1492
# method is not available on the server.
1493
# self._custom_format is always set - the start of initialize ensures
1495
if isinstance(a_bzrdir, RemoteBzrDir):
1496
a_bzrdir._ensure_real()
1497
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1499
# We assume the bzrdir is parameterised; it may not be.
1500
result = self._custom_format.initialize(a_bzrdir)
1501
if (isinstance(a_bzrdir, RemoteBzrDir) and
1502
not isinstance(result, RemoteBranch)):
1503
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1506
def initialize(self, a_bzrdir):
1507
# 1) get the network name to use.
1508
if self._custom_format:
1509
network_name = self._custom_format.network_name()
1511
# Select the current bzrlib default and ask for that.
1512
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1513
reference_format = reference_bzrdir_format.get_branch_format()
1514
self._custom_format = reference_format
1515
network_name = reference_format.network_name()
1516
# Being asked to create on a non RemoteBzrDir:
1517
if not isinstance(a_bzrdir, RemoteBzrDir):
1518
return self._vfs_initialize(a_bzrdir)
1519
medium = a_bzrdir._client._medium
1520
if medium._is_remote_before((1, 13)):
1521
return self._vfs_initialize(a_bzrdir)
1522
# Creating on a remote bzr dir.
1523
# 2) try direct creation via RPC
1524
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1525
verb = 'BzrDir.create_branch'
1527
response = a_bzrdir._call(verb, path, network_name)
1528
except errors.UnknownSmartMethod:
1529
# Fallback - use vfs methods
1530
return self._vfs_initialize(a_bzrdir)
1531
if response[0] != 'ok':
1532
raise errors.UnexpectedSmartServerResponse(response)
1533
# Turn the response into a RemoteRepository object.
1534
format = RemoteBranchFormat()
1535
format._network_name = response[1]
1536
repo_format = response_tuple_to_repo_format(response[3:])
1537
if response[2] == '':
1538
repo_bzrdir = a_bzrdir
1540
repo_bzrdir = RemoteBzrDir(
1541
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1543
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1544
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1545
format=format, setup_stacking=False)
1546
return remote_branch
1548
def supports_tags(self):
1549
# Remote branches might support tags, but we won't know until we
1550
# access the real remote branch.
1554
class RemoteBranch(branch.Branch, _RpcHelper):
1555
"""Branch stored on a server accessed by HPSS RPC.
1557
At the moment most operations are mapped down to simple file operations.
1560
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1561
_client=None, format=None, setup_stacking=True):
1562
"""Create a RemoteBranch instance.
1564
:param real_branch: An optional local implementation of the branch
1565
format, usually accessing the data via the VFS.
1566
:param _client: Private parameter for testing.
1567
:param format: A RemoteBranchFormat object, None to create one
1568
automatically. If supplied it should have a network_name already
1570
:param setup_stacking: If True make an RPC call to determine the
1571
stacked (or not) status of the branch. If False assume the branch
1574
# We intentionally don't call the parent class's __init__, because it
1575
# will try to assign to self.tags, which is a property in this subclass.
1576
# And the parent's __init__ doesn't do much anyway.
1577
self._revision_id_to_revno_cache = None
1578
self._partial_revision_id_to_revno_cache = {}
1579
self._revision_history_cache = None
1580
self._last_revision_info_cache = None
1581
self._merge_sorted_revisions_cache = None
1582
self.bzrdir = remote_bzrdir
1583
if _client is not None:
1584
self._client = _client
1586
self._client = remote_bzrdir._client
1587
self.repository = remote_repository
1588
if real_branch is not None:
1589
self._real_branch = real_branch
1590
# Give the remote repository the matching real repo.
1591
real_repo = self._real_branch.repository
1592
if isinstance(real_repo, RemoteRepository):
1593
real_repo._ensure_real()
1594
real_repo = real_repo._real_repository
1595
self.repository._set_real_repository(real_repo)
1596
# Give the branch the remote repository to let fast-pathing happen.
1597
self._real_branch.repository = self.repository
1599
self._real_branch = None
1600
# Fill out expected attributes of branch for bzrlib api users.
1601
self.base = self.bzrdir.root_transport.base
1602
self._control_files = None
1603
self._lock_mode = None
1604
self._lock_token = None
1605
self._repo_lock_token = None
1606
self._lock_count = 0
1607
self._leave_lock = False
1608
# Setup a format: note that we cannot call _ensure_real until all the
1609
# attributes above are set: This code cannot be moved higher up in this
1612
self._format = RemoteBranchFormat()
1613
if real_branch is not None:
1614
self._format._network_name = \
1615
self._real_branch._format.network_name()
1617
# # XXX: Need to get this from BzrDir.open_branch's return value.
1618
# self._ensure_real()
1619
# self._format._network_name = \
1620
# self._real_branch._format.network_name()
1622
self._format = format
1623
# The base class init is not called, so we duplicate this:
1624
hooks = branch.Branch.hooks['open']
1628
self._setup_stacking()
1630
def _setup_stacking(self):
1631
# configure stacking into the remote repository, by reading it from
1634
fallback_url = self.get_stacked_on_url()
1635
except (errors.NotStacked, errors.UnstackableBranchFormat,
1636
errors.UnstackableRepositoryFormat), e:
1638
# it's relative to this branch...
1639
fallback_url = urlutils.join(self.base, fallback_url)
1640
transports = [self.bzrdir.root_transport]
1641
if self._real_branch is not None:
1642
transports.append(self._real_branch._transport)
1643
stacked_on = branch.Branch.open(fallback_url,
1644
possible_transports=transports)
1645
self.repository.add_fallback_repository(stacked_on.repository)
1647
def _get_real_transport(self):
1648
# if we try vfs access, return the real branch's vfs transport
1650
return self._real_branch._transport
1652
_transport = property(_get_real_transport)
1655
return "%s(%s)" % (self.__class__.__name__, self.base)
1659
def _ensure_real(self):
1660
"""Ensure that there is a _real_branch set.
1662
Used before calls to self._real_branch.
1664
if self._real_branch is None:
1665
if not vfs.vfs_enabled():
1666
raise AssertionError('smart server vfs must be enabled '
1667
'to use vfs implementation')
1668
self.bzrdir._ensure_real()
1669
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1670
if self.repository._real_repository is None:
1671
# Give the remote repository the matching real repo.
1672
real_repo = self._real_branch.repository
1673
if isinstance(real_repo, RemoteRepository):
1674
real_repo._ensure_real()
1675
real_repo = real_repo._real_repository
1676
self.repository._set_real_repository(real_repo)
1677
# Give the real branch the remote repository to let fast-pathing
1679
self._real_branch.repository = self.repository
1680
if self._lock_mode == 'r':
1681
self._real_branch.lock_read()
1682
elif self._lock_mode == 'w':
1683
self._real_branch.lock_write(token=self._lock_token)
1685
def _translate_error(self, err, **context):
1686
self.repository._translate_error(err, branch=self, **context)
1688
def _clear_cached_state(self):
1689
super(RemoteBranch, self)._clear_cached_state()
1690
if self._real_branch is not None:
1691
self._real_branch._clear_cached_state()
1693
def _clear_cached_state_of_remote_branch_only(self):
1694
"""Like _clear_cached_state, but doesn't clear the cache of
1697
This is useful when falling back to calling a method of
1698
self._real_branch that changes state. In that case the underlying
1699
branch changes, so we need to invalidate this RemoteBranch's cache of
1700
it. However, there's no need to invalidate the _real_branch's cache
1701
too, in fact doing so might harm performance.
1703
super(RemoteBranch, self)._clear_cached_state()
1706
def control_files(self):
1707
# Defer actually creating RemoteBranchLockableFiles until its needed,
1708
# because it triggers an _ensure_real that we otherwise might not need.
1709
if self._control_files is None:
1710
self._control_files = RemoteBranchLockableFiles(
1711
self.bzrdir, self._client)
1712
return self._control_files
1714
def _get_checkout_format(self):
1716
return self._real_branch._get_checkout_format()
1718
def get_physical_lock_status(self):
1719
"""See Branch.get_physical_lock_status()."""
1720
# should be an API call to the server, as branches must be lockable.
1722
return self._real_branch.get_physical_lock_status()
1724
def get_stacked_on_url(self):
1725
"""Get the URL this branch is stacked against.
1727
:raises NotStacked: If the branch is not stacked.
1728
:raises UnstackableBranchFormat: If the branch does not support
1730
:raises UnstackableRepositoryFormat: If the repository does not support
1734
# there may not be a repository yet, so we can't use
1735
# self._translate_error, so we can't use self._call either.
1736
response = self._client.call('Branch.get_stacked_on_url',
1737
self._remote_path())
1738
except errors.ErrorFromSmartServer, err:
1739
# there may not be a repository yet, so we can't call through
1740
# its _translate_error
1741
_translate_error(err, branch=self)
1742
except errors.UnknownSmartMethod, err:
1744
return self._real_branch.get_stacked_on_url()
1745
if response[0] != 'ok':
1746
raise errors.UnexpectedSmartServerResponse(response)
1749
def lock_read(self):
1750
self.repository.lock_read()
1751
if not self._lock_mode:
1752
self._lock_mode = 'r'
1753
self._lock_count = 1
1754
if self._real_branch is not None:
1755
self._real_branch.lock_read()
1757
self._lock_count += 1
1759
def _remote_lock_write(self, token):
1761
branch_token = repo_token = ''
1763
branch_token = token
1764
repo_token = self.repository.lock_write()
1765
self.repository.unlock()
1766
err_context = {'token': token}
1767
response = self._call(
1768
'Branch.lock_write', self._remote_path(), branch_token,
1769
repo_token or '', **err_context)
1770
if response[0] != 'ok':
1771
raise errors.UnexpectedSmartServerResponse(response)
1772
ok, branch_token, repo_token = response
1773
return branch_token, repo_token
1775
def lock_write(self, token=None):
1776
if not self._lock_mode:
1777
# Lock the branch and repo in one remote call.
1778
remote_tokens = self._remote_lock_write(token)
1779
self._lock_token, self._repo_lock_token = remote_tokens
1780
if not self._lock_token:
1781
raise SmartProtocolError('Remote server did not return a token!')
1782
# Tell the self.repository object that it is locked.
1783
self.repository.lock_write(
1784
self._repo_lock_token, _skip_rpc=True)
1786
if self._real_branch is not None:
1787
self._real_branch.lock_write(token=self._lock_token)
1788
if token is not None:
1789
self._leave_lock = True
1791
self._leave_lock = False
1792
self._lock_mode = 'w'
1793
self._lock_count = 1
1794
elif self._lock_mode == 'r':
1795
raise errors.ReadOnlyTransaction
1797
if token is not None:
1798
# A token was given to lock_write, and we're relocking, so
1799
# check that the given token actually matches the one we
1801
if token != self._lock_token:
1802
raise errors.TokenMismatch(token, self._lock_token)
1803
self._lock_count += 1
1804
# Re-lock the repository too.
1805
self.repository.lock_write(self._repo_lock_token)
1806
return self._lock_token or None
1808
def _unlock(self, branch_token, repo_token):
1809
err_context = {'token': str((branch_token, repo_token))}
1810
response = self._call(
1811
'Branch.unlock', self._remote_path(), branch_token,
1812
repo_token or '', **err_context)
1813
if response == ('ok',):
1815
raise errors.UnexpectedSmartServerResponse(response)
1819
self._lock_count -= 1
1820
if not self._lock_count:
1821
self._clear_cached_state()
1822
mode = self._lock_mode
1823
self._lock_mode = None
1824
if self._real_branch is not None:
1825
if (not self._leave_lock and mode == 'w' and
1826
self._repo_lock_token):
1827
# If this RemoteBranch will remove the physical lock
1828
# for the repository, make sure the _real_branch
1829
# doesn't do it first. (Because the _real_branch's
1830
# repository is set to be the RemoteRepository.)
1831
self._real_branch.repository.leave_lock_in_place()
1832
self._real_branch.unlock()
1834
# Only write-locked branched need to make a remote method
1835
# call to perfom the unlock.
1837
if not self._lock_token:
1838
raise AssertionError('Locked, but no token!')
1839
branch_token = self._lock_token
1840
repo_token = self._repo_lock_token
1841
self._lock_token = None
1842
self._repo_lock_token = None
1843
if not self._leave_lock:
1844
self._unlock(branch_token, repo_token)
1846
self.repository.unlock()
1848
def break_lock(self):
1850
return self._real_branch.break_lock()
1852
def leave_lock_in_place(self):
1853
if not self._lock_token:
1854
raise NotImplementedError(self.leave_lock_in_place)
1855
self._leave_lock = True
1857
def dont_leave_lock_in_place(self):
1858
if not self._lock_token:
1859
raise NotImplementedError(self.dont_leave_lock_in_place)
1860
self._leave_lock = False
1862
def _last_revision_info(self):
1863
response = self._call('Branch.last_revision_info', self._remote_path())
1864
if response[0] != 'ok':
1865
raise SmartProtocolError('unexpected response code %s' % (response,))
1866
revno = int(response[1])
1867
last_revision = response[2]
1868
return (revno, last_revision)
1870
def _gen_revision_history(self):
1871
"""See Branch._gen_revision_history()."""
1872
response_tuple, response_handler = self._call_expecting_body(
1873
'Branch.revision_history', self._remote_path())
1874
if response_tuple[0] != 'ok':
1875
raise errors.UnexpectedSmartServerResponse(response_tuple)
1876
result = response_handler.read_body_bytes().split('\x00')
1881
def _remote_path(self):
1882
return self.bzrdir._path_for_remote_call(self._client)
1884
def _set_last_revision_descendant(self, revision_id, other_branch,
1885
allow_diverged=False, allow_overwrite_descendant=False):
1886
# This performs additional work to meet the hook contract; while its
1887
# undesirable, we have to synthesise the revno to call the hook, and
1888
# not calling the hook is worse as it means changes can't be prevented.
1889
# Having calculated this though, we can't just call into
1890
# set_last_revision_info as a simple call, because there is a set_rh
1891
# hook that some folk may still be using.
1892
old_revno, old_revid = self.last_revision_info()
1893
history = self._lefthand_history(revision_id)
1894
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1895
err_context = {'other_branch': other_branch}
1896
response = self._call('Branch.set_last_revision_ex',
1897
self._remote_path(), self._lock_token, self._repo_lock_token,
1898
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1900
self._clear_cached_state()
1901
if len(response) != 3 and response[0] != 'ok':
1902
raise errors.UnexpectedSmartServerResponse(response)
1903
new_revno, new_revision_id = response[1:]
1904
self._last_revision_info_cache = new_revno, new_revision_id
1905
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1906
if self._real_branch is not None:
1907
cache = new_revno, new_revision_id
1908
self._real_branch._last_revision_info_cache = cache
1910
def _set_last_revision(self, revision_id):
1911
old_revno, old_revid = self.last_revision_info()
1912
# This performs additional work to meet the hook contract; while its
1913
# undesirable, we have to synthesise the revno to call the hook, and
1914
# not calling the hook is worse as it means changes can't be prevented.
1915
# Having calculated this though, we can't just call into
1916
# set_last_revision_info as a simple call, because there is a set_rh
1917
# hook that some folk may still be using.
1918
history = self._lefthand_history(revision_id)
1919
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1920
self._clear_cached_state()
1921
response = self._call('Branch.set_last_revision',
1922
self._remote_path(), self._lock_token, self._repo_lock_token,
1924
if response != ('ok',):
1925
raise errors.UnexpectedSmartServerResponse(response)
1926
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1929
def set_revision_history(self, rev_history):
1930
# Send just the tip revision of the history; the server will generate
1931
# the full history from that. If the revision doesn't exist in this
1932
# branch, NoSuchRevision will be raised.
1933
if rev_history == []:
1936
rev_id = rev_history[-1]
1937
self._set_last_revision(rev_id)
1938
for hook in branch.Branch.hooks['set_rh']:
1939
hook(self, rev_history)
1940
self._cache_revision_history(rev_history)
1942
def get_parent(self):
1944
return self._real_branch.get_parent()
1946
def _get_parent_location(self):
1947
# Used by tests, when checking normalisation of given vs stored paths.
1949
return self._real_branch._get_parent_location()
1951
def set_parent(self, url):
1953
return self._real_branch.set_parent(url)
1955
def _set_parent_location(self, url):
1956
# Used by tests, to poke bad urls into branch configurations
1958
self.set_parent(url)
1961
return self._real_branch._set_parent_location(url)
1963
def set_stacked_on_url(self, stacked_location):
1964
"""Set the URL this branch is stacked against.
1966
:raises UnstackableBranchFormat: If the branch does not support
1968
:raises UnstackableRepositoryFormat: If the repository does not support
1972
return self._real_branch.set_stacked_on_url(stacked_location)
1975
def pull(self, source, overwrite=False, stop_revision=None,
1977
self._clear_cached_state_of_remote_branch_only()
1979
return self._real_branch.pull(
1980
source, overwrite=overwrite, stop_revision=stop_revision,
1981
_override_hook_target=self, **kwargs)
1984
def push(self, target, overwrite=False, stop_revision=None):
1986
return self._real_branch.push(
1987
target, overwrite=overwrite, stop_revision=stop_revision,
1988
_override_hook_source_branch=self)
1990
def is_locked(self):
1991
return self._lock_count >= 1
1994
def revision_id_to_revno(self, revision_id):
1996
return self._real_branch.revision_id_to_revno(revision_id)
1999
def set_last_revision_info(self, revno, revision_id):
2000
# XXX: These should be returned by the set_last_revision_info verb
2001
old_revno, old_revid = self.last_revision_info()
2002
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2003
revision_id = ensure_null(revision_id)
2005
response = self._call('Branch.set_last_revision_info',
2006
self._remote_path(), self._lock_token, self._repo_lock_token,
2007
str(revno), revision_id)
2008
except errors.UnknownSmartMethod:
2010
self._clear_cached_state_of_remote_branch_only()
2011
self._real_branch.set_last_revision_info(revno, revision_id)
2012
self._last_revision_info_cache = revno, revision_id
2014
if response == ('ok',):
2015
self._clear_cached_state()
2016
self._last_revision_info_cache = revno, revision_id
2017
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2018
# Update the _real_branch's cache too.
2019
if self._real_branch is not None:
2020
cache = self._last_revision_info_cache
2021
self._real_branch._last_revision_info_cache = cache
2023
raise errors.UnexpectedSmartServerResponse(response)
2026
def generate_revision_history(self, revision_id, last_rev=None,
2028
medium = self._client._medium
2029
if not medium._is_remote_before((1, 6)):
2030
# Use a smart method for 1.6 and above servers
2032
self._set_last_revision_descendant(revision_id, other_branch,
2033
allow_diverged=True, allow_overwrite_descendant=True)
2035
except errors.UnknownSmartMethod:
2036
medium._remember_remote_is_before((1, 6))
2037
self._clear_cached_state_of_remote_branch_only()
2038
self.set_revision_history(self._lefthand_history(revision_id,
2039
last_rev=last_rev,other_branch=other_branch))
2044
return self._real_branch.tags
2046
def set_push_location(self, location):
2048
return self._real_branch.set_push_location(location)
2051
def update_revisions(self, other, stop_revision=None, overwrite=False,
2053
"""See Branch.update_revisions."""
2056
if stop_revision is None:
2057
stop_revision = other.last_revision()
2058
if revision.is_null(stop_revision):
2059
# if there are no commits, we're done.
2061
self.fetch(other, stop_revision)
2064
# Just unconditionally set the new revision. We don't care if
2065
# the branches have diverged.
2066
self._set_last_revision(stop_revision)
2068
medium = self._client._medium
2069
if not medium._is_remote_before((1, 6)):
2071
self._set_last_revision_descendant(stop_revision, other)
2073
except errors.UnknownSmartMethod:
2074
medium._remember_remote_is_before((1, 6))
2075
# Fallback for pre-1.6 servers: check for divergence
2076
# client-side, then do _set_last_revision.
2077
last_rev = revision.ensure_null(self.last_revision())
2079
graph = self.repository.get_graph()
2080
if self._check_if_descendant_or_diverged(
2081
stop_revision, last_rev, graph, other):
2082
# stop_revision is a descendant of last_rev, but we aren't
2083
# overwriting, so we're done.
2085
self._set_last_revision(stop_revision)
2090
def _extract_tar(tar, to_dir):
2091
"""Extract all the contents of a tarfile object.
2093
A replacement for extractall, which is not present in python2.4
2096
tar.extract(tarinfo, to_dir)
2099
def _translate_error(err, **context):
2100
"""Translate an ErrorFromSmartServer into a more useful error.
2102
Possible context keys:
2110
If the error from the server doesn't match a known pattern, then
2111
UnknownErrorFromSmartServer is raised.
2115
return context[name]
2116
except KeyError, key_err:
2117
mutter('Missing key %r in context %r', key_err.args[0], context)
2120
"""Get the path from the context if present, otherwise use first error
2124
return context['path']
2125
except KeyError, key_err:
2127
return err.error_args[0]
2128
except IndexError, idx_err:
2130
'Missing key %r in context %r', key_err.args[0], context)
2133
if err.error_verb == 'NoSuchRevision':
2134
raise NoSuchRevision(find('branch'), err.error_args[0])
2135
elif err.error_verb == 'nosuchrevision':
2136
raise NoSuchRevision(find('repository'), err.error_args[0])
2137
elif err.error_tuple == ('nobranch',):
2138
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2139
elif err.error_verb == 'norepository':
2140
raise errors.NoRepositoryPresent(find('bzrdir'))
2141
elif err.error_verb == 'LockContention':
2142
raise errors.LockContention('(remote lock)')
2143
elif err.error_verb == 'UnlockableTransport':
2144
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2145
elif err.error_verb == 'LockFailed':
2146
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2147
elif err.error_verb == 'TokenMismatch':
2148
raise errors.TokenMismatch(find('token'), '(remote token)')
2149
elif err.error_verb == 'Diverged':
2150
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2151
elif err.error_verb == 'TipChangeRejected':
2152
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2153
elif err.error_verb == 'UnstackableBranchFormat':
2154
raise errors.UnstackableBranchFormat(*err.error_args)
2155
elif err.error_verb == 'UnstackableRepositoryFormat':
2156
raise errors.UnstackableRepositoryFormat(*err.error_args)
2157
elif err.error_verb == 'NotStacked':
2158
raise errors.NotStacked(branch=find('branch'))
2159
elif err.error_verb == 'PermissionDenied':
2161
if len(err.error_args) >= 2:
2162
extra = err.error_args[1]
2165
raise errors.PermissionDenied(path, extra=extra)
2166
elif err.error_verb == 'ReadError':
2168
raise errors.ReadError(path)
2169
elif err.error_verb == 'NoSuchFile':
2171
raise errors.NoSuchFile(path)
2172
elif err.error_verb == 'FileExists':
2173
raise errors.FileExists(err.error_args[0])
2174
elif err.error_verb == 'DirectoryNotEmpty':
2175
raise errors.DirectoryNotEmpty(err.error_args[0])
2176
elif err.error_verb == 'ShortReadvError':
2177
args = err.error_args
2178
raise errors.ShortReadvError(
2179
args[0], int(args[1]), int(args[2]), int(args[3]))
2180
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2181
encoding = str(err.error_args[0]) # encoding must always be a string
2182
val = err.error_args[1]
2183
start = int(err.error_args[2])
2184
end = int(err.error_args[3])
2185
reason = str(err.error_args[4]) # reason must always be a string
2186
if val.startswith('u:'):
2187
val = val[2:].decode('utf-8')
2188
elif val.startswith('s:'):
2189
val = val[2:].decode('base64')
2190
if err.error_verb == 'UnicodeDecodeError':
2191
raise UnicodeDecodeError(encoding, val, start, end, reason)
2192
elif err.error_verb == 'UnicodeEncodeError':
2193
raise UnicodeEncodeError(encoding, val, start, end, reason)
2194
elif err.error_verb == 'ReadOnlyError':
2195
raise errors.TransportNotPossible('readonly transport')
2196
raise errors.UnknownErrorFromSmartServer(err)