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)
1368
def _insert_real(self, stream, src_format, resume_tokens):
1369
self.target_repo._ensure_real()
1370
sink = self.target_repo._real_repository._get_sink()
1371
result = sink.insert_stream(stream, src_format, resume_tokens)
1373
self.target_repo.autopack()
1376
def insert_stream(self, stream, src_format, resume_tokens):
1377
repo = self.target_repo
1378
client = repo._client
1379
medium = client._medium
1380
if medium._is_remote_before((1, 13)):
1381
# No possible way this can work.
1382
return self._insert_real(stream, src_format, resume_tokens)
1383
path = repo.bzrdir._path_for_remote_call(client)
1384
if not resume_tokens:
1385
# XXX: Ugly but important for correctness, *will* be fixed during
1386
# 1.13 cycle. Pushing a stream that is interrupted results in a
1387
# fallback to the _real_repositories sink *with a partial stream*.
1388
# Thats bad because we insert less data than bzr expected. To avoid
1389
# this we do a trial push to make sure the verb is accessible, and
1390
# do not fallback when actually pushing the stream. A cleanup patch
1391
# is going to look at rewinding/restarting the stream/partial
1393
byte_stream = self._stream_to_byte_stream([], src_format)
1395
response = client.call_with_body_stream(
1396
('Repository.insert_stream', path, ''), byte_stream)
1397
except errors.UnknownSmartMethod:
1398
medium._remember_remote_is_before((1,13))
1399
return self._insert_real(stream, src_format, resume_tokens)
1400
byte_stream = self._stream_to_byte_stream(stream, src_format)
1401
resume_tokens = ' '.join(resume_tokens)
1402
response = client.call_with_body_stream(
1403
('Repository.insert_stream', path, resume_tokens), byte_stream)
1404
if response[0][0] not in ('ok', 'missing-basis'):
1405
raise errors.UnexpectedSmartServerResponse(response)
1406
if response[0][0] == 'missing-basis':
1407
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1408
resume_tokens = tokens
1409
return resume_tokens, missing_keys
1411
if self.target_repo._real_repository is not None:
1412
collection = getattr(self.target_repo._real_repository,
1413
'_pack_collection', None)
1414
if collection is not None:
1415
collection.reload_pack_names()
1418
def _stream_to_byte_stream(self, stream, src_format):
1420
pack_writer = pack.ContainerWriter(bytes.append)
1422
pack_writer.add_bytes_record(src_format.network_name(), '')
1424
def get_adapter(adapter_key):
1426
return adapters[adapter_key]
1428
adapter_factory = adapter_registry.get(adapter_key)
1429
adapter = adapter_factory(self)
1430
adapters[adapter_key] = adapter
1432
for substream_type, substream in stream:
1433
for record in substream:
1434
if record.storage_kind in ('chunked', 'fulltext'):
1435
serialised = record_to_fulltext_bytes(record)
1437
serialised = record.get_bytes_as(record.storage_kind)
1438
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1447
class RemoteBranchLockableFiles(LockableFiles):
1448
"""A 'LockableFiles' implementation that talks to a smart server.
1450
This is not a public interface class.
1453
def __init__(self, bzrdir, _client):
1454
self.bzrdir = bzrdir
1455
self._client = _client
1456
self._need_find_modes = True
1457
LockableFiles.__init__(
1458
self, bzrdir.get_branch_transport(None),
1459
'lock', lockdir.LockDir)
1461
def _find_modes(self):
1462
# RemoteBranches don't let the client set the mode of control files.
1463
self._dir_mode = None
1464
self._file_mode = None
1467
class RemoteBranchFormat(branch.BranchFormat):
1470
super(RemoteBranchFormat, self).__init__()
1471
self._matchingbzrdir = RemoteBzrDirFormat()
1472
self._matchingbzrdir.set_branch_format(self)
1473
self._custom_format = None
1475
def __eq__(self, other):
1476
return (isinstance(other, RemoteBranchFormat) and
1477
self.__dict__ == other.__dict__)
1479
def get_format_description(self):
1480
return 'Remote BZR Branch'
1482
def network_name(self):
1483
return self._network_name
1485
def open(self, a_bzrdir):
1486
return a_bzrdir.open_branch()
1488
def _vfs_initialize(self, a_bzrdir):
1489
# Initialisation when using a local bzrdir object, or a non-vfs init
1490
# method is not available on the server.
1491
# self._custom_format is always set - the start of initialize ensures
1493
if isinstance(a_bzrdir, RemoteBzrDir):
1494
a_bzrdir._ensure_real()
1495
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1497
# We assume the bzrdir is parameterised; it may not be.
1498
result = self._custom_format.initialize(a_bzrdir)
1499
if (isinstance(a_bzrdir, RemoteBzrDir) and
1500
not isinstance(result, RemoteBranch)):
1501
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1504
def initialize(self, a_bzrdir):
1505
# 1) get the network name to use.
1506
if self._custom_format:
1507
network_name = self._custom_format.network_name()
1509
# Select the current bzrlib default and ask for that.
1510
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1511
reference_format = reference_bzrdir_format.get_branch_format()
1512
self._custom_format = reference_format
1513
network_name = reference_format.network_name()
1514
# Being asked to create on a non RemoteBzrDir:
1515
if not isinstance(a_bzrdir, RemoteBzrDir):
1516
return self._vfs_initialize(a_bzrdir)
1517
medium = a_bzrdir._client._medium
1518
if medium._is_remote_before((1, 13)):
1519
return self._vfs_initialize(a_bzrdir)
1520
# Creating on a remote bzr dir.
1521
# 2) try direct creation via RPC
1522
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1523
verb = 'BzrDir.create_branch'
1525
response = a_bzrdir._call(verb, path, network_name)
1526
except errors.UnknownSmartMethod:
1527
# Fallback - use vfs methods
1528
return self._vfs_initialize(a_bzrdir)
1529
if response[0] != 'ok':
1530
raise errors.UnexpectedSmartServerResponse(response)
1531
# Turn the response into a RemoteRepository object.
1532
format = RemoteBranchFormat()
1533
format._network_name = response[1]
1534
repo_format = response_tuple_to_repo_format(response[3:])
1535
if response[2] == '':
1536
repo_bzrdir = a_bzrdir
1538
repo_bzrdir = RemoteBzrDir(
1539
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1541
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1542
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1543
format=format, setup_stacking=False)
1544
return remote_branch
1546
def supports_tags(self):
1547
# Remote branches might support tags, but we won't know until we
1548
# access the real remote branch.
1552
class RemoteBranch(branch.Branch, _RpcHelper):
1553
"""Branch stored on a server accessed by HPSS RPC.
1555
At the moment most operations are mapped down to simple file operations.
1558
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1559
_client=None, format=None, setup_stacking=True):
1560
"""Create a RemoteBranch instance.
1562
:param real_branch: An optional local implementation of the branch
1563
format, usually accessing the data via the VFS.
1564
:param _client: Private parameter for testing.
1565
:param format: A RemoteBranchFormat object, None to create one
1566
automatically. If supplied it should have a network_name already
1568
:param setup_stacking: If True make an RPC call to determine the
1569
stacked (or not) status of the branch. If False assume the branch
1572
# We intentionally don't call the parent class's __init__, because it
1573
# will try to assign to self.tags, which is a property in this subclass.
1574
# And the parent's __init__ doesn't do much anyway.
1575
self._revision_id_to_revno_cache = None
1576
self._partial_revision_id_to_revno_cache = {}
1577
self._revision_history_cache = None
1578
self._last_revision_info_cache = None
1579
self._merge_sorted_revisions_cache = None
1580
self.bzrdir = remote_bzrdir
1581
if _client is not None:
1582
self._client = _client
1584
self._client = remote_bzrdir._client
1585
self.repository = remote_repository
1586
if real_branch is not None:
1587
self._real_branch = real_branch
1588
# Give the remote repository the matching real repo.
1589
real_repo = self._real_branch.repository
1590
if isinstance(real_repo, RemoteRepository):
1591
real_repo._ensure_real()
1592
real_repo = real_repo._real_repository
1593
self.repository._set_real_repository(real_repo)
1594
# Give the branch the remote repository to let fast-pathing happen.
1595
self._real_branch.repository = self.repository
1597
self._real_branch = None
1598
# Fill out expected attributes of branch for bzrlib api users.
1599
self.base = self.bzrdir.root_transport.base
1600
self._control_files = None
1601
self._lock_mode = None
1602
self._lock_token = None
1603
self._repo_lock_token = None
1604
self._lock_count = 0
1605
self._leave_lock = False
1606
# Setup a format: note that we cannot call _ensure_real until all the
1607
# attributes above are set: This code cannot be moved higher up in this
1610
self._format = RemoteBranchFormat()
1611
if real_branch is not None:
1612
self._format._network_name = \
1613
self._real_branch._format.network_name()
1615
# # XXX: Need to get this from BzrDir.open_branch's return value.
1616
# self._ensure_real()
1617
# self._format._network_name = \
1618
# self._real_branch._format.network_name()
1620
self._format = format
1621
# The base class init is not called, so we duplicate this:
1622
hooks = branch.Branch.hooks['open']
1626
self._setup_stacking()
1628
def _setup_stacking(self):
1629
# configure stacking into the remote repository, by reading it from
1632
fallback_url = self.get_stacked_on_url()
1633
except (errors.NotStacked, errors.UnstackableBranchFormat,
1634
errors.UnstackableRepositoryFormat), e:
1636
# it's relative to this branch...
1637
fallback_url = urlutils.join(self.base, fallback_url)
1638
transports = [self.bzrdir.root_transport]
1639
if self._real_branch is not None:
1640
transports.append(self._real_branch._transport)
1641
stacked_on = branch.Branch.open(fallback_url,
1642
possible_transports=transports)
1643
self.repository.add_fallback_repository(stacked_on.repository)
1645
def _get_real_transport(self):
1646
# if we try vfs access, return the real branch's vfs transport
1648
return self._real_branch._transport
1650
_transport = property(_get_real_transport)
1653
return "%s(%s)" % (self.__class__.__name__, self.base)
1657
def _ensure_real(self):
1658
"""Ensure that there is a _real_branch set.
1660
Used before calls to self._real_branch.
1662
if self._real_branch is None:
1663
if not vfs.vfs_enabled():
1664
raise AssertionError('smart server vfs must be enabled '
1665
'to use vfs implementation')
1666
self.bzrdir._ensure_real()
1667
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1668
if self.repository._real_repository is None:
1669
# Give the remote repository the matching real repo.
1670
real_repo = self._real_branch.repository
1671
if isinstance(real_repo, RemoteRepository):
1672
real_repo._ensure_real()
1673
real_repo = real_repo._real_repository
1674
self.repository._set_real_repository(real_repo)
1675
# Give the real branch the remote repository to let fast-pathing
1677
self._real_branch.repository = self.repository
1678
if self._lock_mode == 'r':
1679
self._real_branch.lock_read()
1680
elif self._lock_mode == 'w':
1681
self._real_branch.lock_write(token=self._lock_token)
1683
def _translate_error(self, err, **context):
1684
self.repository._translate_error(err, branch=self, **context)
1686
def _clear_cached_state(self):
1687
super(RemoteBranch, self)._clear_cached_state()
1688
if self._real_branch is not None:
1689
self._real_branch._clear_cached_state()
1691
def _clear_cached_state_of_remote_branch_only(self):
1692
"""Like _clear_cached_state, but doesn't clear the cache of
1695
This is useful when falling back to calling a method of
1696
self._real_branch that changes state. In that case the underlying
1697
branch changes, so we need to invalidate this RemoteBranch's cache of
1698
it. However, there's no need to invalidate the _real_branch's cache
1699
too, in fact doing so might harm performance.
1701
super(RemoteBranch, self)._clear_cached_state()
1704
def control_files(self):
1705
# Defer actually creating RemoteBranchLockableFiles until its needed,
1706
# because it triggers an _ensure_real that we otherwise might not need.
1707
if self._control_files is None:
1708
self._control_files = RemoteBranchLockableFiles(
1709
self.bzrdir, self._client)
1710
return self._control_files
1712
def _get_checkout_format(self):
1714
return self._real_branch._get_checkout_format()
1716
def get_physical_lock_status(self):
1717
"""See Branch.get_physical_lock_status()."""
1718
# should be an API call to the server, as branches must be lockable.
1720
return self._real_branch.get_physical_lock_status()
1722
def get_stacked_on_url(self):
1723
"""Get the URL this branch is stacked against.
1725
:raises NotStacked: If the branch is not stacked.
1726
:raises UnstackableBranchFormat: If the branch does not support
1728
:raises UnstackableRepositoryFormat: If the repository does not support
1732
# there may not be a repository yet, so we can't use
1733
# self._translate_error, so we can't use self._call either.
1734
response = self._client.call('Branch.get_stacked_on_url',
1735
self._remote_path())
1736
except errors.ErrorFromSmartServer, err:
1737
# there may not be a repository yet, so we can't call through
1738
# its _translate_error
1739
_translate_error(err, branch=self)
1740
except errors.UnknownSmartMethod, err:
1742
return self._real_branch.get_stacked_on_url()
1743
if response[0] != 'ok':
1744
raise errors.UnexpectedSmartServerResponse(response)
1747
def lock_read(self):
1748
self.repository.lock_read()
1749
if not self._lock_mode:
1750
self._lock_mode = 'r'
1751
self._lock_count = 1
1752
if self._real_branch is not None:
1753
self._real_branch.lock_read()
1755
self._lock_count += 1
1757
def _remote_lock_write(self, token):
1759
branch_token = repo_token = ''
1761
branch_token = token
1762
repo_token = self.repository.lock_write()
1763
self.repository.unlock()
1764
err_context = {'token': token}
1765
response = self._call(
1766
'Branch.lock_write', self._remote_path(), branch_token,
1767
repo_token or '', **err_context)
1768
if response[0] != 'ok':
1769
raise errors.UnexpectedSmartServerResponse(response)
1770
ok, branch_token, repo_token = response
1771
return branch_token, repo_token
1773
def lock_write(self, token=None):
1774
if not self._lock_mode:
1775
# Lock the branch and repo in one remote call.
1776
remote_tokens = self._remote_lock_write(token)
1777
self._lock_token, self._repo_lock_token = remote_tokens
1778
if not self._lock_token:
1779
raise SmartProtocolError('Remote server did not return a token!')
1780
# Tell the self.repository object that it is locked.
1781
self.repository.lock_write(
1782
self._repo_lock_token, _skip_rpc=True)
1784
if self._real_branch is not None:
1785
self._real_branch.lock_write(token=self._lock_token)
1786
if token is not None:
1787
self._leave_lock = True
1789
self._leave_lock = False
1790
self._lock_mode = 'w'
1791
self._lock_count = 1
1792
elif self._lock_mode == 'r':
1793
raise errors.ReadOnlyTransaction
1795
if token is not None:
1796
# A token was given to lock_write, and we're relocking, so
1797
# check that the given token actually matches the one we
1799
if token != self._lock_token:
1800
raise errors.TokenMismatch(token, self._lock_token)
1801
self._lock_count += 1
1802
# Re-lock the repository too.
1803
self.repository.lock_write(self._repo_lock_token)
1804
return self._lock_token or None
1806
def _unlock(self, branch_token, repo_token):
1807
err_context = {'token': str((branch_token, repo_token))}
1808
response = self._call(
1809
'Branch.unlock', self._remote_path(), branch_token,
1810
repo_token or '', **err_context)
1811
if response == ('ok',):
1813
raise errors.UnexpectedSmartServerResponse(response)
1817
self._lock_count -= 1
1818
if not self._lock_count:
1819
self._clear_cached_state()
1820
mode = self._lock_mode
1821
self._lock_mode = None
1822
if self._real_branch is not None:
1823
if (not self._leave_lock and mode == 'w' and
1824
self._repo_lock_token):
1825
# If this RemoteBranch will remove the physical lock
1826
# for the repository, make sure the _real_branch
1827
# doesn't do it first. (Because the _real_branch's
1828
# repository is set to be the RemoteRepository.)
1829
self._real_branch.repository.leave_lock_in_place()
1830
self._real_branch.unlock()
1832
# Only write-locked branched need to make a remote method
1833
# call to perfom the unlock.
1835
if not self._lock_token:
1836
raise AssertionError('Locked, but no token!')
1837
branch_token = self._lock_token
1838
repo_token = self._repo_lock_token
1839
self._lock_token = None
1840
self._repo_lock_token = None
1841
if not self._leave_lock:
1842
self._unlock(branch_token, repo_token)
1844
self.repository.unlock()
1846
def break_lock(self):
1848
return self._real_branch.break_lock()
1850
def leave_lock_in_place(self):
1851
if not self._lock_token:
1852
raise NotImplementedError(self.leave_lock_in_place)
1853
self._leave_lock = True
1855
def dont_leave_lock_in_place(self):
1856
if not self._lock_token:
1857
raise NotImplementedError(self.dont_leave_lock_in_place)
1858
self._leave_lock = False
1860
def _last_revision_info(self):
1861
response = self._call('Branch.last_revision_info', self._remote_path())
1862
if response[0] != 'ok':
1863
raise SmartProtocolError('unexpected response code %s' % (response,))
1864
revno = int(response[1])
1865
last_revision = response[2]
1866
return (revno, last_revision)
1868
def _gen_revision_history(self):
1869
"""See Branch._gen_revision_history()."""
1870
response_tuple, response_handler = self._call_expecting_body(
1871
'Branch.revision_history', self._remote_path())
1872
if response_tuple[0] != 'ok':
1873
raise errors.UnexpectedSmartServerResponse(response_tuple)
1874
result = response_handler.read_body_bytes().split('\x00')
1879
def _remote_path(self):
1880
return self.bzrdir._path_for_remote_call(self._client)
1882
def _set_last_revision_descendant(self, revision_id, other_branch,
1883
allow_diverged=False, allow_overwrite_descendant=False):
1884
# This performs additional work to meet the hook contract; while its
1885
# undesirable, we have to synthesise the revno to call the hook, and
1886
# not calling the hook is worse as it means changes can't be prevented.
1887
# Having calculated this though, we can't just call into
1888
# set_last_revision_info as a simple call, because there is a set_rh
1889
# hook that some folk may still be using.
1890
old_revno, old_revid = self.last_revision_info()
1891
history = self._lefthand_history(revision_id)
1892
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1893
err_context = {'other_branch': other_branch}
1894
response = self._call('Branch.set_last_revision_ex',
1895
self._remote_path(), self._lock_token, self._repo_lock_token,
1896
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1898
self._clear_cached_state()
1899
if len(response) != 3 and response[0] != 'ok':
1900
raise errors.UnexpectedSmartServerResponse(response)
1901
new_revno, new_revision_id = response[1:]
1902
self._last_revision_info_cache = new_revno, new_revision_id
1903
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1904
if self._real_branch is not None:
1905
cache = new_revno, new_revision_id
1906
self._real_branch._last_revision_info_cache = cache
1908
def _set_last_revision(self, revision_id):
1909
old_revno, old_revid = self.last_revision_info()
1910
# This performs additional work to meet the hook contract; while its
1911
# undesirable, we have to synthesise the revno to call the hook, and
1912
# not calling the hook is worse as it means changes can't be prevented.
1913
# Having calculated this though, we can't just call into
1914
# set_last_revision_info as a simple call, because there is a set_rh
1915
# hook that some folk may still be using.
1916
history = self._lefthand_history(revision_id)
1917
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1918
self._clear_cached_state()
1919
response = self._call('Branch.set_last_revision',
1920
self._remote_path(), self._lock_token, self._repo_lock_token,
1922
if response != ('ok',):
1923
raise errors.UnexpectedSmartServerResponse(response)
1924
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1927
def set_revision_history(self, rev_history):
1928
# Send just the tip revision of the history; the server will generate
1929
# the full history from that. If the revision doesn't exist in this
1930
# branch, NoSuchRevision will be raised.
1931
if rev_history == []:
1934
rev_id = rev_history[-1]
1935
self._set_last_revision(rev_id)
1936
for hook in branch.Branch.hooks['set_rh']:
1937
hook(self, rev_history)
1938
self._cache_revision_history(rev_history)
1940
def get_parent(self):
1942
return self._real_branch.get_parent()
1944
def _get_parent_location(self):
1945
# Used by tests, when checking normalisation of given vs stored paths.
1947
return self._real_branch._get_parent_location()
1949
def set_parent(self, url):
1951
return self._real_branch.set_parent(url)
1953
def _set_parent_location(self, url):
1954
# Used by tests, to poke bad urls into branch configurations
1956
self.set_parent(url)
1959
return self._real_branch._set_parent_location(url)
1961
def set_stacked_on_url(self, stacked_location):
1962
"""Set the URL this branch is stacked against.
1964
:raises UnstackableBranchFormat: If the branch does not support
1966
:raises UnstackableRepositoryFormat: If the repository does not support
1970
return self._real_branch.set_stacked_on_url(stacked_location)
1973
def pull(self, source, overwrite=False, stop_revision=None,
1975
self._clear_cached_state_of_remote_branch_only()
1977
return self._real_branch.pull(
1978
source, overwrite=overwrite, stop_revision=stop_revision,
1979
_override_hook_target=self, **kwargs)
1982
def push(self, target, overwrite=False, stop_revision=None):
1984
return self._real_branch.push(
1985
target, overwrite=overwrite, stop_revision=stop_revision,
1986
_override_hook_source_branch=self)
1988
def is_locked(self):
1989
return self._lock_count >= 1
1992
def revision_id_to_revno(self, revision_id):
1994
return self._real_branch.revision_id_to_revno(revision_id)
1997
def set_last_revision_info(self, revno, revision_id):
1998
# XXX: These should be returned by the set_last_revision_info verb
1999
old_revno, old_revid = self.last_revision_info()
2000
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2001
revision_id = ensure_null(revision_id)
2003
response = self._call('Branch.set_last_revision_info',
2004
self._remote_path(), self._lock_token, self._repo_lock_token,
2005
str(revno), revision_id)
2006
except errors.UnknownSmartMethod:
2008
self._clear_cached_state_of_remote_branch_only()
2009
self._real_branch.set_last_revision_info(revno, revision_id)
2010
self._last_revision_info_cache = revno, revision_id
2012
if response == ('ok',):
2013
self._clear_cached_state()
2014
self._last_revision_info_cache = revno, revision_id
2015
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2016
# Update the _real_branch's cache too.
2017
if self._real_branch is not None:
2018
cache = self._last_revision_info_cache
2019
self._real_branch._last_revision_info_cache = cache
2021
raise errors.UnexpectedSmartServerResponse(response)
2024
def generate_revision_history(self, revision_id, last_rev=None,
2026
medium = self._client._medium
2027
if not medium._is_remote_before((1, 6)):
2028
# Use a smart method for 1.6 and above servers
2030
self._set_last_revision_descendant(revision_id, other_branch,
2031
allow_diverged=True, allow_overwrite_descendant=True)
2033
except errors.UnknownSmartMethod:
2034
medium._remember_remote_is_before((1, 6))
2035
self._clear_cached_state_of_remote_branch_only()
2036
self.set_revision_history(self._lefthand_history(revision_id,
2037
last_rev=last_rev,other_branch=other_branch))
2042
return self._real_branch.tags
2044
def set_push_location(self, location):
2046
return self._real_branch.set_push_location(location)
2049
def update_revisions(self, other, stop_revision=None, overwrite=False,
2051
"""See Branch.update_revisions."""
2054
if stop_revision is None:
2055
stop_revision = other.last_revision()
2056
if revision.is_null(stop_revision):
2057
# if there are no commits, we're done.
2059
self.fetch(other, stop_revision)
2062
# Just unconditionally set the new revision. We don't care if
2063
# the branches have diverged.
2064
self._set_last_revision(stop_revision)
2066
medium = self._client._medium
2067
if not medium._is_remote_before((1, 6)):
2069
self._set_last_revision_descendant(stop_revision, other)
2071
except errors.UnknownSmartMethod:
2072
medium._remember_remote_is_before((1, 6))
2073
# Fallback for pre-1.6 servers: check for divergence
2074
# client-side, then do _set_last_revision.
2075
last_rev = revision.ensure_null(self.last_revision())
2077
graph = self.repository.get_graph()
2078
if self._check_if_descendant_or_diverged(
2079
stop_revision, last_rev, graph, other):
2080
# stop_revision is a descendant of last_rev, but we aren't
2081
# overwriting, so we're done.
2083
self._set_last_revision(stop_revision)
2088
def _extract_tar(tar, to_dir):
2089
"""Extract all the contents of a tarfile object.
2091
A replacement for extractall, which is not present in python2.4
2094
tar.extract(tarinfo, to_dir)
2097
def _translate_error(err, **context):
2098
"""Translate an ErrorFromSmartServer into a more useful error.
2100
Possible context keys:
2108
If the error from the server doesn't match a known pattern, then
2109
UnknownErrorFromSmartServer is raised.
2113
return context[name]
2114
except KeyError, key_err:
2115
mutter('Missing key %r in context %r', key_err.args[0], context)
2118
"""Get the path from the context if present, otherwise use first error
2122
return context['path']
2123
except KeyError, key_err:
2125
return err.error_args[0]
2126
except IndexError, idx_err:
2128
'Missing key %r in context %r', key_err.args[0], context)
2131
if err.error_verb == 'NoSuchRevision':
2132
raise NoSuchRevision(find('branch'), err.error_args[0])
2133
elif err.error_verb == 'nosuchrevision':
2134
raise NoSuchRevision(find('repository'), err.error_args[0])
2135
elif err.error_tuple == ('nobranch',):
2136
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2137
elif err.error_verb == 'norepository':
2138
raise errors.NoRepositoryPresent(find('bzrdir'))
2139
elif err.error_verb == 'LockContention':
2140
raise errors.LockContention('(remote lock)')
2141
elif err.error_verb == 'UnlockableTransport':
2142
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2143
elif err.error_verb == 'LockFailed':
2144
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2145
elif err.error_verb == 'TokenMismatch':
2146
raise errors.TokenMismatch(find('token'), '(remote token)')
2147
elif err.error_verb == 'Diverged':
2148
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2149
elif err.error_verb == 'TipChangeRejected':
2150
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2151
elif err.error_verb == 'UnstackableBranchFormat':
2152
raise errors.UnstackableBranchFormat(*err.error_args)
2153
elif err.error_verb == 'UnstackableRepositoryFormat':
2154
raise errors.UnstackableRepositoryFormat(*err.error_args)
2155
elif err.error_verb == 'NotStacked':
2156
raise errors.NotStacked(branch=find('branch'))
2157
elif err.error_verb == 'PermissionDenied':
2159
if len(err.error_args) >= 2:
2160
extra = err.error_args[1]
2163
raise errors.PermissionDenied(path, extra=extra)
2164
elif err.error_verb == 'ReadError':
2166
raise errors.ReadError(path)
2167
elif err.error_verb == 'NoSuchFile':
2169
raise errors.NoSuchFile(path)
2170
elif err.error_verb == 'FileExists':
2171
raise errors.FileExists(err.error_args[0])
2172
elif err.error_verb == 'DirectoryNotEmpty':
2173
raise errors.DirectoryNotEmpty(err.error_args[0])
2174
elif err.error_verb == 'ShortReadvError':
2175
args = err.error_args
2176
raise errors.ShortReadvError(
2177
args[0], int(args[1]), int(args[2]), int(args[3]))
2178
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2179
encoding = str(err.error_args[0]) # encoding must always be a string
2180
val = err.error_args[1]
2181
start = int(err.error_args[2])
2182
end = int(err.error_args[3])
2183
reason = str(err.error_args[4]) # reason must always be a string
2184
if val.startswith('u:'):
2185
val = val[2:].decode('utf-8')
2186
elif val.startswith('s:'):
2187
val = val[2:].decode('base64')
2188
if err.error_verb == 'UnicodeDecodeError':
2189
raise UnicodeDecodeError(encoding, val, start, end, reason)
2190
elif err.error_verb == 'UnicodeEncodeError':
2191
raise UnicodeEncodeError(encoding, val, start, end, reason)
2192
elif err.error_verb == 'ReadOnlyError':
2193
raise errors.TransportNotPossible('readonly transport')
2194
raise errors.UnknownErrorFromSmartServer(err)