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
869
self._fallback_repositories.append(repository)
870
# If self._real_repository was parameterised already (e.g. because a
871
# _real_branch had its get_stacked_on_url method called), then the
872
# repository to be added may already be in the _real_repositories list.
873
if self._real_repository is not None:
874
if repository not in self._real_repository._fallback_repositories:
875
self._real_repository.add_fallback_repository(repository)
877
# They are also seen by the fallback repository. If it doesn't
878
# exist yet they'll be added then. This implicitly copies them.
881
def add_inventory(self, revid, inv, parents):
883
return self._real_repository.add_inventory(revid, inv, parents)
885
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
888
return self._real_repository.add_inventory_by_delta(basis_revision_id,
889
delta, new_revision_id, parents)
891
def add_revision(self, rev_id, rev, inv=None, config=None):
893
return self._real_repository.add_revision(
894
rev_id, rev, inv=inv, config=config)
897
def get_inventory(self, revision_id):
899
return self._real_repository.get_inventory(revision_id)
901
def iter_inventories(self, revision_ids):
903
return self._real_repository.iter_inventories(revision_ids)
906
def get_revision(self, revision_id):
908
return self._real_repository.get_revision(revision_id)
910
def get_transaction(self):
912
return self._real_repository.get_transaction()
915
def clone(self, a_bzrdir, revision_id=None):
917
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
919
def make_working_trees(self):
920
"""See Repository.make_working_trees"""
922
return self._real_repository.make_working_trees()
924
def revision_ids_to_search_result(self, result_set):
925
"""Convert a set of revision ids to a graph SearchResult."""
926
result_parents = set()
927
for parents in self.get_graph().get_parent_map(
928
result_set).itervalues():
929
result_parents.update(parents)
930
included_keys = result_set.intersection(result_parents)
931
start_keys = result_set.difference(included_keys)
932
exclude_keys = result_parents.difference(result_set)
933
result = graph.SearchResult(start_keys, exclude_keys,
934
len(result_set), result_set)
938
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
939
"""Return the revision ids that other has that this does not.
941
These are returned in topological order.
943
revision_id: only return revision ids included by revision_id.
945
return repository.InterRepository.get(
946
other, self).search_missing_revision_ids(revision_id, find_ghosts)
948
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
949
# Not delegated to _real_repository so that InterRepository.get has a
950
# chance to find an InterRepository specialised for RemoteRepository.
951
if self.has_same_location(source):
952
# check that last_revision is in 'from' and then return a
954
if (revision_id is not None and
955
not revision.is_null(revision_id)):
956
self.get_revision(revision_id)
958
inter = repository.InterRepository.get(source, self)
960
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
961
except NotImplementedError:
962
raise errors.IncompatibleRepositories(source, self)
964
def create_bundle(self, target, base, fileobj, format=None):
966
self._real_repository.create_bundle(target, base, fileobj, format)
969
def get_ancestry(self, revision_id, topo_sorted=True):
971
return self._real_repository.get_ancestry(revision_id, topo_sorted)
973
def fileids_altered_by_revision_ids(self, revision_ids):
975
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
977
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
979
return self._real_repository._get_versioned_file_checker(
980
revisions, revision_versions_cache)
982
def iter_files_bytes(self, desired_files):
983
"""See Repository.iter_file_bytes.
986
return self._real_repository.iter_files_bytes(desired_files)
989
def _fetch_order(self):
990
"""Decorate the real repository for now.
992
In the long term getting this back from the remote repository as part
993
of open would be more efficient.
996
return self._real_repository._fetch_order
999
def _fetch_uses_deltas(self):
1000
"""Decorate the real repository for now.
1002
In the long term getting this back from the remote repository as part
1003
of open would be more efficient.
1006
return self._real_repository._fetch_uses_deltas
1009
def _fetch_reconcile(self):
1010
"""Decorate the real repository for now.
1012
In the long term getting this back from the remote repository as part
1013
of open would be more efficient.
1016
return self._real_repository._fetch_reconcile
1018
def get_parent_map(self, revision_ids):
1019
"""See bzrlib.Graph.get_parent_map()."""
1020
return self._make_parents_provider().get_parent_map(revision_ids)
1022
def _get_parent_map_rpc(self, keys):
1023
"""Helper for get_parent_map that performs the RPC."""
1024
medium = self._client._medium
1025
if medium._is_remote_before((1, 2)):
1026
# We already found out that the server can't understand
1027
# Repository.get_parent_map requests, so just fetch the whole
1029
# XXX: Note that this will issue a deprecation warning. This is ok
1030
# :- its because we're working with a deprecated server anyway, and
1031
# the user will almost certainly have seen a warning about the
1032
# server version already.
1033
rg = self.get_revision_graph()
1034
# There is an api discrepency between get_parent_map and
1035
# get_revision_graph. Specifically, a "key:()" pair in
1036
# get_revision_graph just means a node has no parents. For
1037
# "get_parent_map" it means the node is a ghost. So fix up the
1038
# graph to correct this.
1039
# https://bugs.launchpad.net/bzr/+bug/214894
1040
# There is one other "bug" which is that ghosts in
1041
# get_revision_graph() are not returned at all. But we won't worry
1042
# about that for now.
1043
for node_id, parent_ids in rg.iteritems():
1044
if parent_ids == ():
1045
rg[node_id] = (NULL_REVISION,)
1046
rg[NULL_REVISION] = ()
1051
raise ValueError('get_parent_map(None) is not valid')
1052
if NULL_REVISION in keys:
1053
keys.discard(NULL_REVISION)
1054
found_parents = {NULL_REVISION:()}
1056
return found_parents
1059
# TODO(Needs analysis): We could assume that the keys being requested
1060
# from get_parent_map are in a breadth first search, so typically they
1061
# will all be depth N from some common parent, and we don't have to
1062
# have the server iterate from the root parent, but rather from the
1063
# keys we're searching; and just tell the server the keyspace we
1064
# already have; but this may be more traffic again.
1066
# Transform self._parents_map into a search request recipe.
1067
# TODO: Manage this incrementally to avoid covering the same path
1068
# repeatedly. (The server will have to on each request, but the less
1069
# work done the better).
1070
parents_map = self._unstacked_provider.get_cached_map()
1071
if parents_map is None:
1072
# Repository is not locked, so there's no cache.
1074
start_set = set(parents_map)
1075
result_parents = set()
1076
for parents in parents_map.itervalues():
1077
result_parents.update(parents)
1078
stop_keys = result_parents.difference(start_set)
1079
included_keys = start_set.intersection(result_parents)
1080
start_set.difference_update(included_keys)
1081
recipe = (start_set, stop_keys, len(parents_map))
1082
body = self._serialise_search_recipe(recipe)
1083
path = self.bzrdir._path_for_remote_call(self._client)
1085
if type(key) is not str:
1087
"key %r not a plain string" % (key,))
1088
verb = 'Repository.get_parent_map'
1089
args = (path,) + tuple(keys)
1091
response = self._call_with_body_bytes_expecting_body(
1093
except errors.UnknownSmartMethod:
1094
# Server does not support this method, so get the whole graph.
1095
# Worse, we have to force a disconnection, because the server now
1096
# doesn't realise it has a body on the wire to consume, so the
1097
# only way to recover is to abandon the connection.
1099
'Server is too old for fast get_parent_map, reconnecting. '
1100
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1102
# To avoid having to disconnect repeatedly, we keep track of the
1103
# fact the server doesn't understand remote methods added in 1.2.
1104
medium._remember_remote_is_before((1, 2))
1105
return self.get_revision_graph(None)
1106
response_tuple, response_handler = response
1107
if response_tuple[0] not in ['ok']:
1108
response_handler.cancel_read_body()
1109
raise errors.UnexpectedSmartServerResponse(response_tuple)
1110
if response_tuple[0] == 'ok':
1111
coded = bz2.decompress(response_handler.read_body_bytes())
1113
# no revisions found
1115
lines = coded.split('\n')
1118
d = tuple(line.split())
1120
revision_graph[d[0]] = d[1:]
1122
# No parents - so give the Graph result (NULL_REVISION,).
1123
revision_graph[d[0]] = (NULL_REVISION,)
1124
return revision_graph
1127
def get_signature_text(self, revision_id):
1129
return self._real_repository.get_signature_text(revision_id)
1132
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1133
def get_revision_graph_with_ghosts(self, revision_ids=None):
1135
return self._real_repository.get_revision_graph_with_ghosts(
1136
revision_ids=revision_ids)
1139
def get_inventory_xml(self, revision_id):
1141
return self._real_repository.get_inventory_xml(revision_id)
1143
def deserialise_inventory(self, revision_id, xml):
1145
return self._real_repository.deserialise_inventory(revision_id, xml)
1147
def reconcile(self, other=None, thorough=False):
1149
return self._real_repository.reconcile(other=other, thorough=thorough)
1151
def all_revision_ids(self):
1153
return self._real_repository.all_revision_ids()
1156
def get_deltas_for_revisions(self, revisions):
1158
return self._real_repository.get_deltas_for_revisions(revisions)
1161
def get_revision_delta(self, revision_id):
1163
return self._real_repository.get_revision_delta(revision_id)
1166
def revision_trees(self, revision_ids):
1168
return self._real_repository.revision_trees(revision_ids)
1171
def get_revision_reconcile(self, revision_id):
1173
return self._real_repository.get_revision_reconcile(revision_id)
1176
def check(self, revision_ids=None):
1178
return self._real_repository.check(revision_ids=revision_ids)
1180
def copy_content_into(self, destination, revision_id=None):
1182
return self._real_repository.copy_content_into(
1183
destination, revision_id=revision_id)
1185
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1186
# get a tarball of the remote repository, and copy from that into the
1188
from bzrlib import osutils
1190
# TODO: Maybe a progress bar while streaming the tarball?
1191
note("Copying repository content as tarball...")
1192
tar_file = self._get_tarball('bz2')
1193
if tar_file is None:
1195
destination = to_bzrdir.create_repository()
1197
tar = tarfile.open('repository', fileobj=tar_file,
1199
tmpdir = osutils.mkdtemp()
1201
_extract_tar(tar, tmpdir)
1202
tmp_bzrdir = BzrDir.open(tmpdir)
1203
tmp_repo = tmp_bzrdir.open_repository()
1204
tmp_repo.copy_content_into(destination, revision_id)
1206
osutils.rmtree(tmpdir)
1210
# TODO: Suggestion from john: using external tar is much faster than
1211
# python's tarfile library, but it may not work on windows.
1214
def inventories(self):
1215
"""Decorate the real repository for now.
1217
In the long term a full blown network facility is needed to
1218
avoid creating a real repository object locally.
1221
return self._real_repository.inventories
1225
"""Compress the data within the repository.
1227
This is not currently implemented within the smart server.
1230
return self._real_repository.pack()
1233
def revisions(self):
1234
"""Decorate the real repository for now.
1236
In the short term this should become a real object to intercept graph
1239
In the long term a full blown network facility is needed.
1242
return self._real_repository.revisions
1244
def set_make_working_trees(self, new_value):
1246
new_value_str = "True"
1248
new_value_str = "False"
1249
path = self.bzrdir._path_for_remote_call(self._client)
1251
response = self._call(
1252
'Repository.set_make_working_trees', path, new_value_str)
1253
except errors.UnknownSmartMethod:
1255
self._real_repository.set_make_working_trees(new_value)
1257
if response[0] != 'ok':
1258
raise errors.UnexpectedSmartServerResponse(response)
1261
def signatures(self):
1262
"""Decorate the real repository for now.
1264
In the long term a full blown network facility is needed to avoid
1265
creating a real repository object locally.
1268
return self._real_repository.signatures
1271
def sign_revision(self, revision_id, gpg_strategy):
1273
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1277
"""Decorate the real repository for now.
1279
In the long term a full blown network facility is needed to avoid
1280
creating a real repository object locally.
1283
return self._real_repository.texts
1286
def get_revisions(self, revision_ids):
1288
return self._real_repository.get_revisions(revision_ids)
1290
def supports_rich_root(self):
1292
return self._real_repository.supports_rich_root()
1294
def iter_reverse_revision_history(self, revision_id):
1296
return self._real_repository.iter_reverse_revision_history(revision_id)
1299
def _serializer(self):
1301
return self._real_repository._serializer
1303
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1305
return self._real_repository.store_revision_signature(
1306
gpg_strategy, plaintext, revision_id)
1308
def add_signature_text(self, revision_id, signature):
1310
return self._real_repository.add_signature_text(revision_id, signature)
1312
def has_signature_for_revision_id(self, revision_id):
1314
return self._real_repository.has_signature_for_revision_id(revision_id)
1316
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1318
return self._real_repository.item_keys_introduced_by(revision_ids,
1319
_files_pb=_files_pb)
1321
def revision_graph_can_have_wrong_parents(self):
1322
# The answer depends on the remote repo format.
1324
return self._real_repository.revision_graph_can_have_wrong_parents()
1326
def _find_inconsistent_revision_parents(self):
1328
return self._real_repository._find_inconsistent_revision_parents()
1330
def _check_for_inconsistent_revision_parents(self):
1332
return self._real_repository._check_for_inconsistent_revision_parents()
1334
def _make_parents_provider(self, other=None):
1335
providers = [self._unstacked_provider]
1336
if other is not None:
1337
providers.insert(0, other)
1338
providers.extend(r._make_parents_provider() for r in
1339
self._fallback_repositories)
1340
return graph._StackedParentsProvider(providers)
1342
def _serialise_search_recipe(self, recipe):
1343
"""Serialise a graph search recipe.
1345
:param recipe: A search recipe (start, stop, count).
1346
:return: Serialised bytes.
1348
start_keys = ' '.join(recipe[0])
1349
stop_keys = ' '.join(recipe[1])
1350
count = str(recipe[2])
1351
return '\n'.join((start_keys, stop_keys, count))
1354
path = self.bzrdir._path_for_remote_call(self._client)
1356
response = self._call('PackRepository.autopack', path)
1357
except errors.UnknownSmartMethod:
1359
self._real_repository._pack_collection.autopack()
1361
if self._real_repository is not None:
1362
# Reset the real repository's cache of pack names.
1363
# XXX: At some point we may be able to skip this and just rely on
1364
# the automatic retry logic to do the right thing, but for now we
1365
# err on the side of being correct rather than being optimal.
1366
self._real_repository._pack_collection.reload_pack_names()
1367
if response[0] != 'ok':
1368
raise errors.UnexpectedSmartServerResponse(response)
1371
class RemoteStreamSink(repository.StreamSink):
1373
def __init__(self, target_repo):
1374
repository.StreamSink.__init__(self, target_repo)
1376
def _insert_real(self, stream, src_format, resume_tokens):
1377
self.target_repo._ensure_real()
1378
sink = self.target_repo._real_repository._get_sink()
1379
result = sink.insert_stream(stream, src_format, resume_tokens)
1381
self.target_repo.autopack()
1384
def insert_stream(self, stream, src_format, resume_tokens):
1385
repo = self.target_repo
1386
client = repo._client
1387
medium = client._medium
1388
if medium._is_remote_before((1, 13)):
1389
# No possible way this can work.
1390
return self._insert_real(stream, src_format, resume_tokens)
1391
path = repo.bzrdir._path_for_remote_call(client)
1392
if not resume_tokens:
1393
# XXX: Ugly but important for correctness, *will* be fixed during
1394
# 1.13 cycle. Pushing a stream that is interrupted results in a
1395
# fallback to the _real_repositories sink *with a partial stream*.
1396
# Thats bad because we insert less data than bzr expected. To avoid
1397
# this we do a trial push to make sure the verb is accessible, and
1398
# do not fallback when actually pushing the stream. A cleanup patch
1399
# is going to look at rewinding/restarting the stream/partial
1401
byte_stream = self._stream_to_byte_stream([], src_format)
1403
response = client.call_with_body_stream(
1404
('Repository.insert_stream', path, ''), byte_stream)
1405
except errors.UnknownSmartMethod:
1406
medium._remember_remote_is_before((1,13))
1407
return self._insert_real(stream, src_format, resume_tokens)
1408
byte_stream = self._stream_to_byte_stream(stream, src_format)
1409
resume_tokens = ' '.join(resume_tokens)
1410
response = client.call_with_body_stream(
1411
('Repository.insert_stream', path, resume_tokens), byte_stream)
1412
if response[0][0] not in ('ok', 'missing-basis'):
1413
raise errors.UnexpectedSmartServerResponse(response)
1414
if response[0][0] == 'missing-basis':
1415
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1416
resume_tokens = tokens
1417
return resume_tokens, missing_keys
1419
if self.target_repo._real_repository is not None:
1420
collection = getattr(self.target_repo._real_repository,
1421
'_pack_collection', None)
1422
if collection is not None:
1423
collection.reload_pack_names()
1426
def _stream_to_byte_stream(self, stream, src_format):
1428
pack_writer = pack.ContainerWriter(bytes.append)
1430
pack_writer.add_bytes_record(src_format.network_name(), '')
1432
def get_adapter(adapter_key):
1434
return adapters[adapter_key]
1436
adapter_factory = adapter_registry.get(adapter_key)
1437
adapter = adapter_factory(self)
1438
adapters[adapter_key] = adapter
1440
for substream_type, substream in stream:
1441
for record in substream:
1442
if record.storage_kind in ('chunked', 'fulltext'):
1443
serialised = record_to_fulltext_bytes(record)
1445
serialised = record.get_bytes_as(record.storage_kind)
1446
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1455
class RemoteBranchLockableFiles(LockableFiles):
1456
"""A 'LockableFiles' implementation that talks to a smart server.
1458
This is not a public interface class.
1461
def __init__(self, bzrdir, _client):
1462
self.bzrdir = bzrdir
1463
self._client = _client
1464
self._need_find_modes = True
1465
LockableFiles.__init__(
1466
self, bzrdir.get_branch_transport(None),
1467
'lock', lockdir.LockDir)
1469
def _find_modes(self):
1470
# RemoteBranches don't let the client set the mode of control files.
1471
self._dir_mode = None
1472
self._file_mode = None
1475
class RemoteBranchFormat(branch.BranchFormat):
1478
super(RemoteBranchFormat, self).__init__()
1479
self._matchingbzrdir = RemoteBzrDirFormat()
1480
self._matchingbzrdir.set_branch_format(self)
1481
self._custom_format = None
1483
def __eq__(self, other):
1484
return (isinstance(other, RemoteBranchFormat) and
1485
self.__dict__ == other.__dict__)
1487
def get_format_description(self):
1488
return 'Remote BZR Branch'
1490
def network_name(self):
1491
return self._network_name
1493
def open(self, a_bzrdir):
1494
return a_bzrdir.open_branch()
1496
def _vfs_initialize(self, a_bzrdir):
1497
# Initialisation when using a local bzrdir object, or a non-vfs init
1498
# method is not available on the server.
1499
# self._custom_format is always set - the start of initialize ensures
1501
if isinstance(a_bzrdir, RemoteBzrDir):
1502
a_bzrdir._ensure_real()
1503
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1505
# We assume the bzrdir is parameterised; it may not be.
1506
result = self._custom_format.initialize(a_bzrdir)
1507
if (isinstance(a_bzrdir, RemoteBzrDir) and
1508
not isinstance(result, RemoteBranch)):
1509
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1512
def initialize(self, a_bzrdir):
1513
# 1) get the network name to use.
1514
if self._custom_format:
1515
network_name = self._custom_format.network_name()
1517
# Select the current bzrlib default and ask for that.
1518
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1519
reference_format = reference_bzrdir_format.get_branch_format()
1520
self._custom_format = reference_format
1521
network_name = reference_format.network_name()
1522
# Being asked to create on a non RemoteBzrDir:
1523
if not isinstance(a_bzrdir, RemoteBzrDir):
1524
return self._vfs_initialize(a_bzrdir)
1525
medium = a_bzrdir._client._medium
1526
if medium._is_remote_before((1, 13)):
1527
return self._vfs_initialize(a_bzrdir)
1528
# Creating on a remote bzr dir.
1529
# 2) try direct creation via RPC
1530
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1531
verb = 'BzrDir.create_branch'
1533
response = a_bzrdir._call(verb, path, network_name)
1534
except errors.UnknownSmartMethod:
1535
# Fallback - use vfs methods
1536
return self._vfs_initialize(a_bzrdir)
1537
if response[0] != 'ok':
1538
raise errors.UnexpectedSmartServerResponse(response)
1539
# Turn the response into a RemoteRepository object.
1540
format = RemoteBranchFormat()
1541
format._network_name = response[1]
1542
repo_format = response_tuple_to_repo_format(response[3:])
1543
if response[2] == '':
1544
repo_bzrdir = a_bzrdir
1546
repo_bzrdir = RemoteBzrDir(
1547
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1549
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1550
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1551
format=format, setup_stacking=False)
1552
return remote_branch
1554
def supports_tags(self):
1555
# Remote branches might support tags, but we won't know until we
1556
# access the real remote branch.
1560
class RemoteBranch(branch.Branch, _RpcHelper):
1561
"""Branch stored on a server accessed by HPSS RPC.
1563
At the moment most operations are mapped down to simple file operations.
1566
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1567
_client=None, format=None, setup_stacking=True):
1568
"""Create a RemoteBranch instance.
1570
:param real_branch: An optional local implementation of the branch
1571
format, usually accessing the data via the VFS.
1572
:param _client: Private parameter for testing.
1573
:param format: A RemoteBranchFormat object, None to create one
1574
automatically. If supplied it should have a network_name already
1576
:param setup_stacking: If True make an RPC call to determine the
1577
stacked (or not) status of the branch. If False assume the branch
1580
# We intentionally don't call the parent class's __init__, because it
1581
# will try to assign to self.tags, which is a property in this subclass.
1582
# And the parent's __init__ doesn't do much anyway.
1583
self._revision_id_to_revno_cache = None
1584
self._partial_revision_id_to_revno_cache = {}
1585
self._revision_history_cache = None
1586
self._last_revision_info_cache = None
1587
self._merge_sorted_revisions_cache = None
1588
self.bzrdir = remote_bzrdir
1589
if _client is not None:
1590
self._client = _client
1592
self._client = remote_bzrdir._client
1593
self.repository = remote_repository
1594
if real_branch is not None:
1595
self._real_branch = real_branch
1596
# Give the remote repository the matching real repo.
1597
real_repo = self._real_branch.repository
1598
if isinstance(real_repo, RemoteRepository):
1599
real_repo._ensure_real()
1600
real_repo = real_repo._real_repository
1601
self.repository._set_real_repository(real_repo)
1602
# Give the branch the remote repository to let fast-pathing happen.
1603
self._real_branch.repository = self.repository
1605
self._real_branch = None
1606
# Fill out expected attributes of branch for bzrlib api users.
1607
self.base = self.bzrdir.root_transport.base
1608
self._control_files = None
1609
self._lock_mode = None
1610
self._lock_token = None
1611
self._repo_lock_token = None
1612
self._lock_count = 0
1613
self._leave_lock = False
1614
# Setup a format: note that we cannot call _ensure_real until all the
1615
# attributes above are set: This code cannot be moved higher up in this
1618
self._format = RemoteBranchFormat()
1619
if real_branch is not None:
1620
self._format._network_name = \
1621
self._real_branch._format.network_name()
1623
# # XXX: Need to get this from BzrDir.open_branch's return value.
1624
# self._ensure_real()
1625
# self._format._network_name = \
1626
# self._real_branch._format.network_name()
1628
self._format = format
1629
# The base class init is not called, so we duplicate this:
1630
hooks = branch.Branch.hooks['open']
1634
self._setup_stacking()
1636
def _setup_stacking(self):
1637
# configure stacking into the remote repository, by reading it from
1640
fallback_url = self.get_stacked_on_url()
1641
except (errors.NotStacked, errors.UnstackableBranchFormat,
1642
errors.UnstackableRepositoryFormat), e:
1644
# it's relative to this branch...
1645
fallback_url = urlutils.join(self.base, fallback_url)
1646
transports = [self.bzrdir.root_transport]
1647
if self._real_branch is not None:
1648
# The real repository is setup already:
1649
transports.append(self._real_branch._transport)
1650
self.repository.add_fallback_repository(
1651
self.repository._real_repository._fallback_repositories[0])
1653
stacked_on = branch.Branch.open(fallback_url,
1654
possible_transports=transports)
1655
self.repository.add_fallback_repository(stacked_on.repository)
1657
def _get_real_transport(self):
1658
# if we try vfs access, return the real branch's vfs transport
1660
return self._real_branch._transport
1662
_transport = property(_get_real_transport)
1665
return "%s(%s)" % (self.__class__.__name__, self.base)
1669
def _ensure_real(self):
1670
"""Ensure that there is a _real_branch set.
1672
Used before calls to self._real_branch.
1674
if self._real_branch is None:
1675
if not vfs.vfs_enabled():
1676
raise AssertionError('smart server vfs must be enabled '
1677
'to use vfs implementation')
1678
self.bzrdir._ensure_real()
1679
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1680
if self.repository._real_repository is None:
1681
# Give the remote repository the matching real repo.
1682
real_repo = self._real_branch.repository
1683
if isinstance(real_repo, RemoteRepository):
1684
real_repo._ensure_real()
1685
real_repo = real_repo._real_repository
1686
self.repository._set_real_repository(real_repo)
1687
# Give the real branch the remote repository to let fast-pathing
1689
self._real_branch.repository = self.repository
1690
if self._lock_mode == 'r':
1691
self._real_branch.lock_read()
1692
elif self._lock_mode == 'w':
1693
self._real_branch.lock_write(token=self._lock_token)
1695
def _translate_error(self, err, **context):
1696
self.repository._translate_error(err, branch=self, **context)
1698
def _clear_cached_state(self):
1699
super(RemoteBranch, self)._clear_cached_state()
1700
if self._real_branch is not None:
1701
self._real_branch._clear_cached_state()
1703
def _clear_cached_state_of_remote_branch_only(self):
1704
"""Like _clear_cached_state, but doesn't clear the cache of
1707
This is useful when falling back to calling a method of
1708
self._real_branch that changes state. In that case the underlying
1709
branch changes, so we need to invalidate this RemoteBranch's cache of
1710
it. However, there's no need to invalidate the _real_branch's cache
1711
too, in fact doing so might harm performance.
1713
super(RemoteBranch, self)._clear_cached_state()
1716
def control_files(self):
1717
# Defer actually creating RemoteBranchLockableFiles until its needed,
1718
# because it triggers an _ensure_real that we otherwise might not need.
1719
if self._control_files is None:
1720
self._control_files = RemoteBranchLockableFiles(
1721
self.bzrdir, self._client)
1722
return self._control_files
1724
def _get_checkout_format(self):
1726
return self._real_branch._get_checkout_format()
1728
def get_physical_lock_status(self):
1729
"""See Branch.get_physical_lock_status()."""
1730
# should be an API call to the server, as branches must be lockable.
1732
return self._real_branch.get_physical_lock_status()
1734
def get_stacked_on_url(self):
1735
"""Get the URL this branch is stacked against.
1737
:raises NotStacked: If the branch is not stacked.
1738
:raises UnstackableBranchFormat: If the branch does not support
1740
:raises UnstackableRepositoryFormat: If the repository does not support
1744
# there may not be a repository yet, so we can't use
1745
# self._translate_error, so we can't use self._call either.
1746
response = self._client.call('Branch.get_stacked_on_url',
1747
self._remote_path())
1748
except errors.ErrorFromSmartServer, err:
1749
# there may not be a repository yet, so we can't call through
1750
# its _translate_error
1751
_translate_error(err, branch=self)
1752
except errors.UnknownSmartMethod, err:
1754
return self._real_branch.get_stacked_on_url()
1755
if response[0] != 'ok':
1756
raise errors.UnexpectedSmartServerResponse(response)
1759
def lock_read(self):
1760
self.repository.lock_read()
1761
if not self._lock_mode:
1762
self._lock_mode = 'r'
1763
self._lock_count = 1
1764
if self._real_branch is not None:
1765
self._real_branch.lock_read()
1767
self._lock_count += 1
1769
def _remote_lock_write(self, token):
1771
branch_token = repo_token = ''
1773
branch_token = token
1774
repo_token = self.repository.lock_write()
1775
self.repository.unlock()
1776
err_context = {'token': token}
1777
response = self._call(
1778
'Branch.lock_write', self._remote_path(), branch_token,
1779
repo_token or '', **err_context)
1780
if response[0] != 'ok':
1781
raise errors.UnexpectedSmartServerResponse(response)
1782
ok, branch_token, repo_token = response
1783
return branch_token, repo_token
1785
def lock_write(self, token=None):
1786
if not self._lock_mode:
1787
# Lock the branch and repo in one remote call.
1788
remote_tokens = self._remote_lock_write(token)
1789
self._lock_token, self._repo_lock_token = remote_tokens
1790
if not self._lock_token:
1791
raise SmartProtocolError('Remote server did not return a token!')
1792
# Tell the self.repository object that it is locked.
1793
self.repository.lock_write(
1794
self._repo_lock_token, _skip_rpc=True)
1796
if self._real_branch is not None:
1797
self._real_branch.lock_write(token=self._lock_token)
1798
if token is not None:
1799
self._leave_lock = True
1801
self._leave_lock = False
1802
self._lock_mode = 'w'
1803
self._lock_count = 1
1804
elif self._lock_mode == 'r':
1805
raise errors.ReadOnlyTransaction
1807
if token is not None:
1808
# A token was given to lock_write, and we're relocking, so
1809
# check that the given token actually matches the one we
1811
if token != self._lock_token:
1812
raise errors.TokenMismatch(token, self._lock_token)
1813
self._lock_count += 1
1814
# Re-lock the repository too.
1815
self.repository.lock_write(self._repo_lock_token)
1816
return self._lock_token or None
1818
def _unlock(self, branch_token, repo_token):
1819
err_context = {'token': str((branch_token, repo_token))}
1820
response = self._call(
1821
'Branch.unlock', self._remote_path(), branch_token,
1822
repo_token or '', **err_context)
1823
if response == ('ok',):
1825
raise errors.UnexpectedSmartServerResponse(response)
1829
self._lock_count -= 1
1830
if not self._lock_count:
1831
self._clear_cached_state()
1832
mode = self._lock_mode
1833
self._lock_mode = None
1834
if self._real_branch is not None:
1835
if (not self._leave_lock and mode == 'w' and
1836
self._repo_lock_token):
1837
# If this RemoteBranch will remove the physical lock
1838
# for the repository, make sure the _real_branch
1839
# doesn't do it first. (Because the _real_branch's
1840
# repository is set to be the RemoteRepository.)
1841
self._real_branch.repository.leave_lock_in_place()
1842
self._real_branch.unlock()
1844
# Only write-locked branched need to make a remote method
1845
# call to perfom the unlock.
1847
if not self._lock_token:
1848
raise AssertionError('Locked, but no token!')
1849
branch_token = self._lock_token
1850
repo_token = self._repo_lock_token
1851
self._lock_token = None
1852
self._repo_lock_token = None
1853
if not self._leave_lock:
1854
self._unlock(branch_token, repo_token)
1856
self.repository.unlock()
1858
def break_lock(self):
1860
return self._real_branch.break_lock()
1862
def leave_lock_in_place(self):
1863
if not self._lock_token:
1864
raise NotImplementedError(self.leave_lock_in_place)
1865
self._leave_lock = True
1867
def dont_leave_lock_in_place(self):
1868
if not self._lock_token:
1869
raise NotImplementedError(self.dont_leave_lock_in_place)
1870
self._leave_lock = False
1872
def _last_revision_info(self):
1873
response = self._call('Branch.last_revision_info', self._remote_path())
1874
if response[0] != 'ok':
1875
raise SmartProtocolError('unexpected response code %s' % (response,))
1876
revno = int(response[1])
1877
last_revision = response[2]
1878
return (revno, last_revision)
1880
def _gen_revision_history(self):
1881
"""See Branch._gen_revision_history()."""
1882
response_tuple, response_handler = self._call_expecting_body(
1883
'Branch.revision_history', self._remote_path())
1884
if response_tuple[0] != 'ok':
1885
raise errors.UnexpectedSmartServerResponse(response_tuple)
1886
result = response_handler.read_body_bytes().split('\x00')
1891
def _remote_path(self):
1892
return self.bzrdir._path_for_remote_call(self._client)
1894
def _set_last_revision_descendant(self, revision_id, other_branch,
1895
allow_diverged=False, allow_overwrite_descendant=False):
1896
# This performs additional work to meet the hook contract; while its
1897
# undesirable, we have to synthesise the revno to call the hook, and
1898
# not calling the hook is worse as it means changes can't be prevented.
1899
# Having calculated this though, we can't just call into
1900
# set_last_revision_info as a simple call, because there is a set_rh
1901
# hook that some folk may still be using.
1902
old_revno, old_revid = self.last_revision_info()
1903
history = self._lefthand_history(revision_id)
1904
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1905
err_context = {'other_branch': other_branch}
1906
response = self._call('Branch.set_last_revision_ex',
1907
self._remote_path(), self._lock_token, self._repo_lock_token,
1908
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1910
self._clear_cached_state()
1911
if len(response) != 3 and response[0] != 'ok':
1912
raise errors.UnexpectedSmartServerResponse(response)
1913
new_revno, new_revision_id = response[1:]
1914
self._last_revision_info_cache = new_revno, new_revision_id
1915
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1916
if self._real_branch is not None:
1917
cache = new_revno, new_revision_id
1918
self._real_branch._last_revision_info_cache = cache
1920
def _set_last_revision(self, revision_id):
1921
old_revno, old_revid = self.last_revision_info()
1922
# This performs additional work to meet the hook contract; while its
1923
# undesirable, we have to synthesise the revno to call the hook, and
1924
# not calling the hook is worse as it means changes can't be prevented.
1925
# Having calculated this though, we can't just call into
1926
# set_last_revision_info as a simple call, because there is a set_rh
1927
# hook that some folk may still be using.
1928
history = self._lefthand_history(revision_id)
1929
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1930
self._clear_cached_state()
1931
response = self._call('Branch.set_last_revision',
1932
self._remote_path(), self._lock_token, self._repo_lock_token,
1934
if response != ('ok',):
1935
raise errors.UnexpectedSmartServerResponse(response)
1936
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1939
def set_revision_history(self, rev_history):
1940
# Send just the tip revision of the history; the server will generate
1941
# the full history from that. If the revision doesn't exist in this
1942
# branch, NoSuchRevision will be raised.
1943
if rev_history == []:
1946
rev_id = rev_history[-1]
1947
self._set_last_revision(rev_id)
1948
for hook in branch.Branch.hooks['set_rh']:
1949
hook(self, rev_history)
1950
self._cache_revision_history(rev_history)
1952
def get_parent(self):
1954
return self._real_branch.get_parent()
1956
def _get_parent_location(self):
1957
# Used by tests, when checking normalisation of given vs stored paths.
1959
return self._real_branch._get_parent_location()
1961
def set_parent(self, url):
1963
return self._real_branch.set_parent(url)
1965
def _set_parent_location(self, url):
1966
# Used by tests, to poke bad urls into branch configurations
1968
self.set_parent(url)
1971
return self._real_branch._set_parent_location(url)
1973
def set_stacked_on_url(self, stacked_location):
1974
"""Set the URL this branch is stacked against.
1976
:raises UnstackableBranchFormat: If the branch does not support
1978
:raises UnstackableRepositoryFormat: If the repository does not support
1982
return self._real_branch.set_stacked_on_url(stacked_location)
1985
def pull(self, source, overwrite=False, stop_revision=None,
1987
self._clear_cached_state_of_remote_branch_only()
1989
return self._real_branch.pull(
1990
source, overwrite=overwrite, stop_revision=stop_revision,
1991
_override_hook_target=self, **kwargs)
1994
def push(self, target, overwrite=False, stop_revision=None):
1996
return self._real_branch.push(
1997
target, overwrite=overwrite, stop_revision=stop_revision,
1998
_override_hook_source_branch=self)
2000
def is_locked(self):
2001
return self._lock_count >= 1
2004
def revision_id_to_revno(self, revision_id):
2006
return self._real_branch.revision_id_to_revno(revision_id)
2009
def set_last_revision_info(self, revno, revision_id):
2010
# XXX: These should be returned by the set_last_revision_info verb
2011
old_revno, old_revid = self.last_revision_info()
2012
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2013
revision_id = ensure_null(revision_id)
2015
response = self._call('Branch.set_last_revision_info',
2016
self._remote_path(), self._lock_token, self._repo_lock_token,
2017
str(revno), revision_id)
2018
except errors.UnknownSmartMethod:
2020
self._clear_cached_state_of_remote_branch_only()
2021
self._real_branch.set_last_revision_info(revno, revision_id)
2022
self._last_revision_info_cache = revno, revision_id
2024
if response == ('ok',):
2025
self._clear_cached_state()
2026
self._last_revision_info_cache = revno, revision_id
2027
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2028
# Update the _real_branch's cache too.
2029
if self._real_branch is not None:
2030
cache = self._last_revision_info_cache
2031
self._real_branch._last_revision_info_cache = cache
2033
raise errors.UnexpectedSmartServerResponse(response)
2036
def generate_revision_history(self, revision_id, last_rev=None,
2038
medium = self._client._medium
2039
if not medium._is_remote_before((1, 6)):
2040
# Use a smart method for 1.6 and above servers
2042
self._set_last_revision_descendant(revision_id, other_branch,
2043
allow_diverged=True, allow_overwrite_descendant=True)
2045
except errors.UnknownSmartMethod:
2046
medium._remember_remote_is_before((1, 6))
2047
self._clear_cached_state_of_remote_branch_only()
2048
self.set_revision_history(self._lefthand_history(revision_id,
2049
last_rev=last_rev,other_branch=other_branch))
2054
return self._real_branch.tags
2056
def set_push_location(self, location):
2058
return self._real_branch.set_push_location(location)
2061
def _extract_tar(tar, to_dir):
2062
"""Extract all the contents of a tarfile object.
2064
A replacement for extractall, which is not present in python2.4
2067
tar.extract(tarinfo, to_dir)
2070
def _translate_error(err, **context):
2071
"""Translate an ErrorFromSmartServer into a more useful error.
2073
Possible context keys:
2081
If the error from the server doesn't match a known pattern, then
2082
UnknownErrorFromSmartServer is raised.
2086
return context[name]
2087
except KeyError, key_err:
2088
mutter('Missing key %r in context %r', key_err.args[0], context)
2091
"""Get the path from the context if present, otherwise use first error
2095
return context['path']
2096
except KeyError, key_err:
2098
return err.error_args[0]
2099
except IndexError, idx_err:
2101
'Missing key %r in context %r', key_err.args[0], context)
2104
if err.error_verb == 'NoSuchRevision':
2105
raise NoSuchRevision(find('branch'), err.error_args[0])
2106
elif err.error_verb == 'nosuchrevision':
2107
raise NoSuchRevision(find('repository'), err.error_args[0])
2108
elif err.error_tuple == ('nobranch',):
2109
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2110
elif err.error_verb == 'norepository':
2111
raise errors.NoRepositoryPresent(find('bzrdir'))
2112
elif err.error_verb == 'LockContention':
2113
raise errors.LockContention('(remote lock)')
2114
elif err.error_verb == 'UnlockableTransport':
2115
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2116
elif err.error_verb == 'LockFailed':
2117
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2118
elif err.error_verb == 'TokenMismatch':
2119
raise errors.TokenMismatch(find('token'), '(remote token)')
2120
elif err.error_verb == 'Diverged':
2121
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2122
elif err.error_verb == 'TipChangeRejected':
2123
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2124
elif err.error_verb == 'UnstackableBranchFormat':
2125
raise errors.UnstackableBranchFormat(*err.error_args)
2126
elif err.error_verb == 'UnstackableRepositoryFormat':
2127
raise errors.UnstackableRepositoryFormat(*err.error_args)
2128
elif err.error_verb == 'NotStacked':
2129
raise errors.NotStacked(branch=find('branch'))
2130
elif err.error_verb == 'PermissionDenied':
2132
if len(err.error_args) >= 2:
2133
extra = err.error_args[1]
2136
raise errors.PermissionDenied(path, extra=extra)
2137
elif err.error_verb == 'ReadError':
2139
raise errors.ReadError(path)
2140
elif err.error_verb == 'NoSuchFile':
2142
raise errors.NoSuchFile(path)
2143
elif err.error_verb == 'FileExists':
2144
raise errors.FileExists(err.error_args[0])
2145
elif err.error_verb == 'DirectoryNotEmpty':
2146
raise errors.DirectoryNotEmpty(err.error_args[0])
2147
elif err.error_verb == 'ShortReadvError':
2148
args = err.error_args
2149
raise errors.ShortReadvError(
2150
args[0], int(args[1]), int(args[2]), int(args[3]))
2151
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2152
encoding = str(err.error_args[0]) # encoding must always be a string
2153
val = err.error_args[1]
2154
start = int(err.error_args[2])
2155
end = int(err.error_args[3])
2156
reason = str(err.error_args[4]) # reason must always be a string
2157
if val.startswith('u:'):
2158
val = val[2:].decode('utf-8')
2159
elif val.startswith('s:'):
2160
val = val[2:].decode('base64')
2161
if err.error_verb == 'UnicodeDecodeError':
2162
raise UnicodeDecodeError(encoding, val, start, end, reason)
2163
elif err.error_verb == 'UnicodeEncodeError':
2164
raise UnicodeEncodeError(encoding, val, start, end, reason)
2165
elif err.error_verb == 'ReadOnlyError':
2166
raise errors.TransportNotPossible('readonly transport')
2167
raise errors.UnknownErrorFromSmartServer(err)