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
99
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
100
# create_branch for details.
101
self._next_open_branch_result = None
104
medium = transport.get_smart_medium()
105
self._client = client._SmartClient(medium)
107
self._client = _client
110
path = self._path_for_remote_call(self._client)
111
response = self._call('BzrDir.open', path)
112
if response not in [('yes',), ('no',)]:
113
raise errors.UnexpectedSmartServerResponse(response)
114
if response == ('no',):
115
raise errors.NotBranchError(path=transport.base)
117
def _ensure_real(self):
118
"""Ensure that there is a _real_bzrdir set.
120
Used before calls to self._real_bzrdir.
122
if not self._real_bzrdir:
123
self._real_bzrdir = BzrDir.open_from_transport(
124
self.root_transport, _server_formats=False)
126
def _translate_error(self, err, **context):
127
_translate_error(err, bzrdir=self, **context)
129
def break_lock(self):
130
# Prevent aliasing problems in the next_open_branch_result cache.
131
# See create_branch for rationale.
132
self._next_open_branch_result = None
133
return BzrDir.break_lock(self)
135
def cloning_metadir(self, stacked=False):
137
return self._real_bzrdir.cloning_metadir(stacked)
139
def create_repository(self, shared=False):
140
# as per meta1 formats - just delegate to the format object which may
142
result = self._format.repository_format.initialize(self, shared)
143
if not isinstance(result, RemoteRepository):
144
return self.open_repository()
148
def destroy_repository(self):
149
"""See BzrDir.destroy_repository"""
151
self._real_bzrdir.destroy_repository()
153
def create_branch(self):
154
# as per meta1 formats - just delegate to the format object which may
156
real_branch = self._format.get_branch_format().initialize(self)
157
if not isinstance(real_branch, RemoteBranch):
158
result = RemoteBranch(self, self.find_repository(), real_branch)
161
# BzrDir.clone_on_transport() uses the result of create_branch but does
162
# not return it to its callers; we save approximately 8% of our round
163
# trips by handing the branch we created back to the first caller to
164
# open_branch rather than probing anew. Long term we need a API in
165
# bzrdir that doesn't discard result objects (like result_branch).
167
self._next_open_branch_result = result
170
def destroy_branch(self):
171
"""See BzrDir.destroy_branch"""
173
self._real_bzrdir.destroy_branch()
174
self._next_open_branch_result = None
176
def create_workingtree(self, revision_id=None, from_branch=None):
177
raise errors.NotLocalUrl(self.transport.base)
179
def find_branch_format(self):
180
"""Find the branch 'format' for this bzrdir.
182
This might be a synthetic object for e.g. RemoteBranch and SVN.
184
b = self.open_branch()
187
def get_branch_reference(self):
188
"""See BzrDir.get_branch_reference()."""
189
path = self._path_for_remote_call(self._client)
190
response = self._call('BzrDir.open_branch', path)
191
if response[0] == 'ok':
192
if response[1] == '':
193
# branch at this location.
196
# a branch reference, use the existing BranchReference logic.
199
raise errors.UnexpectedSmartServerResponse(response)
201
def _get_tree_branch(self):
202
"""See BzrDir._get_tree_branch()."""
203
return None, self.open_branch()
205
def open_branch(self, _unsupported=False):
207
raise NotImplementedError('unsupported flag support not implemented yet.')
208
if self._next_open_branch_result is not None:
209
# See create_branch for details.
210
result = self._next_open_branch_result
211
self._next_open_branch_result = None
213
reference_url = self.get_branch_reference()
214
if reference_url is None:
215
# branch at this location.
216
return RemoteBranch(self, self.find_repository())
218
# a branch reference, use the existing BranchReference logic.
219
format = BranchReferenceFormat()
220
return format.open(self, _found=True, location=reference_url)
222
def open_repository(self):
223
path = self._path_for_remote_call(self._client)
224
verb = 'BzrDir.find_repositoryV2'
226
response = self._call(verb, path)
227
except errors.UnknownSmartMethod:
228
verb = 'BzrDir.find_repository'
229
response = self._call(verb, path)
230
if response[0] != 'ok':
231
raise errors.UnexpectedSmartServerResponse(response)
232
if verb == 'BzrDir.find_repository':
233
# servers that don't support the V2 method don't support external
235
response = response + ('no', )
236
if not (len(response) == 5):
237
raise SmartProtocolError('incorrect response length %s' % (response,))
238
if response[1] == '':
239
format = RemoteRepositoryFormat()
240
format.rich_root_data = (response[2] == 'yes')
241
format.supports_tree_reference = (response[3] == 'yes')
242
# No wire format to check this yet.
243
format.supports_external_lookups = (response[4] == 'yes')
244
# Used to support creating a real format instance when needed.
245
format._creating_bzrdir = self
246
remote_repo = RemoteRepository(self, format)
247
format._creating_repo = remote_repo
250
raise errors.NoRepositoryPresent(self)
252
def open_workingtree(self, recommend_upgrade=True):
254
if self._real_bzrdir.has_workingtree():
255
raise errors.NotLocalUrl(self.root_transport)
257
raise errors.NoWorkingTree(self.root_transport.base)
259
def _path_for_remote_call(self, client):
260
"""Return the path to be used for this bzrdir in a remote call."""
261
return client.remote_path_from_transport(self.root_transport)
263
def get_branch_transport(self, branch_format):
265
return self._real_bzrdir.get_branch_transport(branch_format)
267
def get_repository_transport(self, repository_format):
269
return self._real_bzrdir.get_repository_transport(repository_format)
271
def get_workingtree_transport(self, workingtree_format):
273
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
275
def can_convert_format(self):
276
"""Upgrading of remote bzrdirs is not supported yet."""
279
def needs_format_conversion(self, format=None):
280
"""Upgrading of remote bzrdirs is not supported yet."""
282
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
283
% 'needs_format_conversion(format=None)')
286
def clone(self, url, revision_id=None, force_new_repo=False,
287
preserve_stacking=False):
289
return self._real_bzrdir.clone(url, revision_id=revision_id,
290
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
292
def get_config(self):
294
return self._real_bzrdir.get_config()
297
class RemoteRepositoryFormat(repository.RepositoryFormat):
298
"""Format for repositories accessed over a _SmartClient.
300
Instances of this repository are represented by RemoteRepository
303
The RemoteRepositoryFormat is parameterized during construction
304
to reflect the capabilities of the real, remote format. Specifically
305
the attributes rich_root_data and supports_tree_reference are set
306
on a per instance basis, and are not set (and should not be) at
309
:ivar _custom_format: If set, a specific concrete repository format that
310
will be used when initializing a repository with this
311
RemoteRepositoryFormat.
312
:ivar _creating_repo: If set, the repository object that this
313
RemoteRepositoryFormat was created for: it can be called into
314
to obtain data like the network name.
317
_matchingbzrdir = RemoteBzrDirFormat()
320
repository.RepositoryFormat.__init__(self)
321
self._custom_format = None
322
self._network_name = None
323
self._creating_bzrdir = None
325
def _vfs_initialize(self, a_bzrdir, shared):
326
"""Helper for common code in initialize."""
327
if self._custom_format:
328
# Custom format requested
329
result = self._custom_format.initialize(a_bzrdir, shared=shared)
330
elif self._creating_bzrdir is not None:
331
# Use the format that the repository we were created to back
333
prior_repo = self._creating_bzrdir.open_repository()
334
prior_repo._ensure_real()
335
result = prior_repo._real_repository._format.initialize(
336
a_bzrdir, shared=shared)
338
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
339
# support remote initialization.
340
# We delegate to a real object at this point (as RemoteBzrDir
341
# delegate to the repository format which would lead to infinite
342
# recursion if we just called a_bzrdir.create_repository.
343
a_bzrdir._ensure_real()
344
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
345
if not isinstance(result, RemoteRepository):
346
return self.open(a_bzrdir)
350
def initialize(self, a_bzrdir, shared=False):
351
# Being asked to create on a non RemoteBzrDir:
352
if not isinstance(a_bzrdir, RemoteBzrDir):
353
return self._vfs_initialize(a_bzrdir, shared)
354
medium = a_bzrdir._client._medium
355
if medium._is_remote_before((1, 13)):
356
return self._vfs_initialize(a_bzrdir, shared)
357
# Creating on a remote bzr dir.
358
# 1) get the network name to use.
359
if self._custom_format:
360
network_name = self._custom_format.network_name()
362
# Select the current bzrlib default and ask for that.
363
reference_bzrdir_format = bzrdir.format_registry.get('default')()
364
reference_format = reference_bzrdir_format.repository_format
365
network_name = reference_format.network_name()
366
# 2) try direct creation via RPC
367
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
368
verb = 'BzrDir.create_repository'
374
response = a_bzrdir._call(verb, path, network_name, shared_str)
375
except errors.UnknownSmartMethod:
376
# Fallback - use vfs methods
377
return self._vfs_initialize(a_bzrdir, shared)
379
# Turn the response into a RemoteRepository object.
380
format = response_tuple_to_repo_format(response[1:])
381
# Used to support creating a real format instance when needed.
382
format._creating_bzrdir = a_bzrdir
383
remote_repo = RemoteRepository(a_bzrdir, format)
384
format._creating_repo = remote_repo
387
def open(self, a_bzrdir):
388
if not isinstance(a_bzrdir, RemoteBzrDir):
389
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
390
return a_bzrdir.open_repository()
392
def get_format_description(self):
393
return 'bzr remote repository'
395
def __eq__(self, other):
396
return self.__class__ == other.__class__
398
def check_conversion_target(self, target_format):
399
if self.rich_root_data and not target_format.rich_root_data:
400
raise errors.BadConversionTarget(
401
'Does not support rich root data.', target_format)
402
if (self.supports_tree_reference and
403
not getattr(target_format, 'supports_tree_reference', False)):
404
raise errors.BadConversionTarget(
405
'Does not support nested trees', target_format)
407
def network_name(self):
408
if self._network_name:
409
return self._network_name
410
self._creating_repo._ensure_real()
411
return self._creating_repo._real_repository._format.network_name()
414
def _serializer(self):
415
if self._custom_format is not None:
416
return self._custom_format._serializer
417
elif self._network_name is not None:
418
self._custom_format = repository.network_format_registry.get(
420
return self._custom_format._serializer
422
# We should only be getting asked for the serializer for
423
# RemoteRepositoryFormat objects when the RemoteRepositoryFormat object
424
# is a concrete instance for a RemoteRepository. In this case we know
425
# the creating_repo and can use it to supply the serializer.
426
self._creating_repo._ensure_real()
427
return self._creating_repo._real_repository._format._serializer
430
class RemoteRepository(_RpcHelper):
431
"""Repository accessed over rpc.
433
For the moment most operations are performed using local transport-backed
437
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
438
"""Create a RemoteRepository instance.
440
:param remote_bzrdir: The bzrdir hosting this repository.
441
:param format: The RemoteFormat object to use.
442
:param real_repository: If not None, a local implementation of the
443
repository logic for the repository, usually accessing the data
445
:param _client: Private testing parameter - override the smart client
446
to be used by the repository.
449
self._real_repository = real_repository
451
self._real_repository = None
452
self.bzrdir = remote_bzrdir
454
self._client = remote_bzrdir._client
456
self._client = _client
457
self._format = format
458
self._lock_mode = None
459
self._lock_token = None
461
self._leave_lock = False
462
self._unstacked_provider = graph.CachingParentsProvider(
463
get_parent_map=self._get_parent_map_rpc)
464
self._unstacked_provider.disable_cache()
466
# These depend on the actual remote format, so force them off for
467
# maximum compatibility. XXX: In future these should depend on the
468
# remote repository instance, but this is irrelevant until we perform
469
# reconcile via an RPC call.
470
self._reconcile_does_inventory_gc = False
471
self._reconcile_fixes_text_parents = False
472
self._reconcile_backsup_inventory = False
473
self.base = self.bzrdir.transport.base
474
# Additional places to query for data.
475
self._fallback_repositories = []
478
return "%s(%s)" % (self.__class__.__name__, self.base)
482
def abort_write_group(self, suppress_errors=False):
483
"""Complete a write group on the decorated repository.
485
Smart methods peform operations in a single step so this api
486
is not really applicable except as a compatibility thunk
487
for older plugins that don't use e.g. the CommitBuilder
490
:param suppress_errors: see Repository.abort_write_group.
493
return self._real_repository.abort_write_group(
494
suppress_errors=suppress_errors)
496
def commit_write_group(self):
497
"""Complete a write group on the decorated repository.
499
Smart methods peform operations in a single step so this api
500
is not really applicable except as a compatibility thunk
501
for older plugins that don't use e.g. the CommitBuilder
505
return self._real_repository.commit_write_group()
507
def resume_write_group(self, tokens):
509
return self._real_repository.resume_write_group(tokens)
511
def suspend_write_group(self):
513
return self._real_repository.suspend_write_group()
515
def _ensure_real(self):
516
"""Ensure that there is a _real_repository set.
518
Used before calls to self._real_repository.
520
if self._real_repository is None:
521
self.bzrdir._ensure_real()
522
self._set_real_repository(
523
self.bzrdir._real_bzrdir.open_repository())
525
def _translate_error(self, err, **context):
526
self.bzrdir._translate_error(err, repository=self, **context)
528
def find_text_key_references(self):
529
"""Find the text key references within the repository.
531
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
532
revision_ids. Each altered file-ids has the exact revision_ids that
533
altered it listed explicitly.
534
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
535
to whether they were referred to by the inventory of the
536
revision_id that they contain. The inventory texts from all present
537
revision ids are assessed to generate this report.
540
return self._real_repository.find_text_key_references()
542
def _generate_text_key_index(self):
543
"""Generate a new text key index for the repository.
545
This is an expensive function that will take considerable time to run.
547
:return: A dict mapping (file_id, revision_id) tuples to a list of
548
parents, also (file_id, revision_id) tuples.
551
return self._real_repository._generate_text_key_index()
553
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
554
def get_revision_graph(self, revision_id=None):
555
"""See Repository.get_revision_graph()."""
556
return self._get_revision_graph(revision_id)
558
def _get_revision_graph(self, revision_id):
559
"""Private method for using with old (< 1.2) servers to fallback."""
560
if revision_id is None:
562
elif revision.is_null(revision_id):
565
path = self.bzrdir._path_for_remote_call(self._client)
566
response = self._call_expecting_body(
567
'Repository.get_revision_graph', path, revision_id)
568
response_tuple, response_handler = response
569
if response_tuple[0] != 'ok':
570
raise errors.UnexpectedSmartServerResponse(response_tuple)
571
coded = response_handler.read_body_bytes()
573
# no revisions in this repository!
575
lines = coded.split('\n')
578
d = tuple(line.split())
579
revision_graph[d[0]] = d[1:]
581
return revision_graph
584
"""See Repository._get_sink()."""
585
return RemoteStreamSink(self)
587
def has_revision(self, revision_id):
588
"""See Repository.has_revision()."""
589
if revision_id == NULL_REVISION:
590
# The null revision is always present.
592
path = self.bzrdir._path_for_remote_call(self._client)
593
response = self._call('Repository.has_revision', path, revision_id)
594
if response[0] not in ('yes', 'no'):
595
raise errors.UnexpectedSmartServerResponse(response)
596
if response[0] == 'yes':
598
for fallback_repo in self._fallback_repositories:
599
if fallback_repo.has_revision(revision_id):
603
def has_revisions(self, revision_ids):
604
"""See Repository.has_revisions()."""
605
# FIXME: This does many roundtrips, particularly when there are
606
# fallback repositories. -- mbp 20080905
608
for revision_id in revision_ids:
609
if self.has_revision(revision_id):
610
result.add(revision_id)
613
def has_same_location(self, other):
614
return (self.__class__ == other.__class__ and
615
self.bzrdir.transport.base == other.bzrdir.transport.base)
617
def get_graph(self, other_repository=None):
618
"""Return the graph for this repository format"""
619
parents_provider = self._make_parents_provider(other_repository)
620
return graph.Graph(parents_provider)
622
def gather_stats(self, revid=None, committers=None):
623
"""See Repository.gather_stats()."""
624
path = self.bzrdir._path_for_remote_call(self._client)
625
# revid can be None to indicate no revisions, not just NULL_REVISION
626
if revid is None or revision.is_null(revid):
630
if committers is None or not committers:
631
fmt_committers = 'no'
633
fmt_committers = 'yes'
634
response_tuple, response_handler = self._call_expecting_body(
635
'Repository.gather_stats', path, fmt_revid, fmt_committers)
636
if response_tuple[0] != 'ok':
637
raise errors.UnexpectedSmartServerResponse(response_tuple)
639
body = response_handler.read_body_bytes()
641
for line in body.split('\n'):
644
key, val_text = line.split(':')
645
if key in ('revisions', 'size', 'committers'):
646
result[key] = int(val_text)
647
elif key in ('firstrev', 'latestrev'):
648
values = val_text.split(' ')[1:]
649
result[key] = (float(values[0]), long(values[1]))
653
def find_branches(self, using=False):
654
"""See Repository.find_branches()."""
655
# should be an API call to the server.
657
return self._real_repository.find_branches(using=using)
659
def get_physical_lock_status(self):
660
"""See Repository.get_physical_lock_status()."""
661
# should be an API call to the server.
663
return self._real_repository.get_physical_lock_status()
665
def is_in_write_group(self):
666
"""Return True if there is an open write group.
668
write groups are only applicable locally for the smart server..
670
if self._real_repository:
671
return self._real_repository.is_in_write_group()
674
return self._lock_count >= 1
677
"""See Repository.is_shared()."""
678
path = self.bzrdir._path_for_remote_call(self._client)
679
response = self._call('Repository.is_shared', path)
680
if response[0] not in ('yes', 'no'):
681
raise SmartProtocolError('unexpected response code %s' % (response,))
682
return response[0] == 'yes'
684
def is_write_locked(self):
685
return self._lock_mode == 'w'
688
# wrong eventually - want a local lock cache context
689
if not self._lock_mode:
690
self._lock_mode = 'r'
692
self._unstacked_provider.enable_cache(cache_misses=False)
693
if self._real_repository is not None:
694
self._real_repository.lock_read()
696
self._lock_count += 1
698
def _remote_lock_write(self, token):
699
path = self.bzrdir._path_for_remote_call(self._client)
702
err_context = {'token': token}
703
response = self._call('Repository.lock_write', path, token,
705
if response[0] == 'ok':
709
raise errors.UnexpectedSmartServerResponse(response)
711
def lock_write(self, token=None, _skip_rpc=False):
712
if not self._lock_mode:
714
if self._lock_token is not None:
715
if token != self._lock_token:
716
raise errors.TokenMismatch(token, self._lock_token)
717
self._lock_token = token
719
self._lock_token = self._remote_lock_write(token)
720
# if self._lock_token is None, then this is something like packs or
721
# svn where we don't get to lock the repo, or a weave style repository
722
# where we cannot lock it over the wire and attempts to do so will
724
if self._real_repository is not None:
725
self._real_repository.lock_write(token=self._lock_token)
726
if token is not None:
727
self._leave_lock = True
729
self._leave_lock = False
730
self._lock_mode = 'w'
732
self._unstacked_provider.enable_cache(cache_misses=False)
733
elif self._lock_mode == 'r':
734
raise errors.ReadOnlyError(self)
736
self._lock_count += 1
737
return self._lock_token or None
739
def leave_lock_in_place(self):
740
if not self._lock_token:
741
raise NotImplementedError(self.leave_lock_in_place)
742
self._leave_lock = True
744
def dont_leave_lock_in_place(self):
745
if not self._lock_token:
746
raise NotImplementedError(self.dont_leave_lock_in_place)
747
self._leave_lock = False
749
def _set_real_repository(self, repository):
750
"""Set the _real_repository for this repository.
752
:param repository: The repository to fallback to for non-hpss
753
implemented operations.
755
if self._real_repository is not None:
756
raise AssertionError('_real_repository is already set')
757
if isinstance(repository, RemoteRepository):
758
raise AssertionError()
759
self._real_repository = repository
760
for fb in self._fallback_repositories:
761
self._real_repository.add_fallback_repository(fb)
762
if self._lock_mode == 'w':
763
# if we are already locked, the real repository must be able to
764
# acquire the lock with our token.
765
self._real_repository.lock_write(self._lock_token)
766
elif self._lock_mode == 'r':
767
self._real_repository.lock_read()
769
def start_write_group(self):
770
"""Start a write group on the decorated repository.
772
Smart methods peform operations in a single step so this api
773
is not really applicable except as a compatibility thunk
774
for older plugins that don't use e.g. the CommitBuilder
778
return self._real_repository.start_write_group()
780
def _unlock(self, token):
781
path = self.bzrdir._path_for_remote_call(self._client)
783
# with no token the remote repository is not persistently locked.
785
err_context = {'token': token}
786
response = self._call('Repository.unlock', path, token,
788
if response == ('ok',):
791
raise errors.UnexpectedSmartServerResponse(response)
794
if not self._lock_count:
795
raise errors.LockNotHeld(self)
796
self._lock_count -= 1
797
if self._lock_count > 0:
799
self._unstacked_provider.disable_cache()
800
old_mode = self._lock_mode
801
self._lock_mode = None
803
# The real repository is responsible at present for raising an
804
# exception if it's in an unfinished write group. However, it
805
# normally will *not* actually remove the lock from disk - that's
806
# done by the server on receiving the Repository.unlock call.
807
# This is just to let the _real_repository stay up to date.
808
if self._real_repository is not None:
809
self._real_repository.unlock()
811
# The rpc-level lock should be released even if there was a
812
# problem releasing the vfs-based lock.
814
# Only write-locked repositories need to make a remote method
815
# call to perfom the unlock.
816
old_token = self._lock_token
817
self._lock_token = None
818
if not self._leave_lock:
819
self._unlock(old_token)
821
def break_lock(self):
822
# should hand off to the network
824
return self._real_repository.break_lock()
826
def _get_tarball(self, compression):
827
"""Return a TemporaryFile containing a repository tarball.
829
Returns None if the server does not support sending tarballs.
832
path = self.bzrdir._path_for_remote_call(self._client)
834
response, protocol = self._call_expecting_body(
835
'Repository.tarball', path, compression)
836
except errors.UnknownSmartMethod:
837
protocol.cancel_read_body()
839
if response[0] == 'ok':
840
# Extract the tarball and return it
841
t = tempfile.NamedTemporaryFile()
842
# TODO: rpc layer should read directly into it...
843
t.write(protocol.read_body_bytes())
846
raise errors.UnexpectedSmartServerResponse(response)
848
def sprout(self, to_bzrdir, revision_id=None):
849
# TODO: Option to control what format is created?
851
dest_repo = self._real_repository._format.initialize(to_bzrdir,
853
dest_repo.fetch(self, revision_id=revision_id)
856
### These methods are just thin shims to the VFS object for now.
858
def revision_tree(self, revision_id):
860
return self._real_repository.revision_tree(revision_id)
862
def get_serializer_format(self):
864
return self._real_repository.get_serializer_format()
866
def get_commit_builder(self, branch, parents, config, timestamp=None,
867
timezone=None, committer=None, revprops=None,
869
# FIXME: It ought to be possible to call this without immediately
870
# triggering _ensure_real. For now it's the easiest thing to do.
872
real_repo = self._real_repository
873
builder = real_repo.get_commit_builder(branch, parents,
874
config, timestamp=timestamp, timezone=timezone,
875
committer=committer, revprops=revprops, revision_id=revision_id)
878
def add_fallback_repository(self, repository):
879
"""Add a repository to use for looking up data not held locally.
881
:param repository: A repository.
883
# XXX: At the moment the RemoteRepository will allow fallbacks
884
# unconditionally - however, a _real_repository will usually exist,
885
# and may raise an error if it's not accommodated by the underlying
886
# format. Eventually we should check when opening the repository
887
# whether it's willing to allow them or not.
889
# We need to accumulate additional repositories here, to pass them in
892
self._fallback_repositories.append(repository)
893
# If self._real_repository was parameterised already (e.g. because a
894
# _real_branch had its get_stacked_on_url method called), then the
895
# repository to be added may already be in the _real_repositories list.
896
if self._real_repository is not None:
897
if repository not in self._real_repository._fallback_repositories:
898
self._real_repository.add_fallback_repository(repository)
900
# They are also seen by the fallback repository. If it doesn't
901
# exist yet they'll be added then. This implicitly copies them.
904
def add_inventory(self, revid, inv, parents):
906
return self._real_repository.add_inventory(revid, inv, parents)
908
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
911
return self._real_repository.add_inventory_by_delta(basis_revision_id,
912
delta, new_revision_id, parents)
914
def add_revision(self, rev_id, rev, inv=None, config=None):
916
return self._real_repository.add_revision(
917
rev_id, rev, inv=inv, config=config)
920
def get_inventory(self, revision_id):
922
return self._real_repository.get_inventory(revision_id)
924
def iter_inventories(self, revision_ids):
926
return self._real_repository.iter_inventories(revision_ids)
929
def get_revision(self, revision_id):
931
return self._real_repository.get_revision(revision_id)
933
def get_transaction(self):
935
return self._real_repository.get_transaction()
938
def clone(self, a_bzrdir, revision_id=None):
940
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
942
def make_working_trees(self):
943
"""See Repository.make_working_trees"""
945
return self._real_repository.make_working_trees()
947
def revision_ids_to_search_result(self, result_set):
948
"""Convert a set of revision ids to a graph SearchResult."""
949
result_parents = set()
950
for parents in self.get_graph().get_parent_map(
951
result_set).itervalues():
952
result_parents.update(parents)
953
included_keys = result_set.intersection(result_parents)
954
start_keys = result_set.difference(included_keys)
955
exclude_keys = result_parents.difference(result_set)
956
result = graph.SearchResult(start_keys, exclude_keys,
957
len(result_set), result_set)
961
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
962
"""Return the revision ids that other has that this does not.
964
These are returned in topological order.
966
revision_id: only return revision ids included by revision_id.
968
return repository.InterRepository.get(
969
other, self).search_missing_revision_ids(revision_id, find_ghosts)
971
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
972
# Not delegated to _real_repository so that InterRepository.get has a
973
# chance to find an InterRepository specialised for RemoteRepository.
974
if self.has_same_location(source):
975
# check that last_revision is in 'from' and then return a
977
if (revision_id is not None and
978
not revision.is_null(revision_id)):
979
self.get_revision(revision_id)
981
inter = repository.InterRepository.get(source, self)
983
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
984
except NotImplementedError:
985
raise errors.IncompatibleRepositories(source, self)
987
def create_bundle(self, target, base, fileobj, format=None):
989
self._real_repository.create_bundle(target, base, fileobj, format)
992
def get_ancestry(self, revision_id, topo_sorted=True):
994
return self._real_repository.get_ancestry(revision_id, topo_sorted)
996
def fileids_altered_by_revision_ids(self, revision_ids):
998
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1000
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1002
return self._real_repository._get_versioned_file_checker(
1003
revisions, revision_versions_cache)
1005
def iter_files_bytes(self, desired_files):
1006
"""See Repository.iter_file_bytes.
1009
return self._real_repository.iter_files_bytes(desired_files)
1012
def _fetch_order(self):
1013
"""Decorate the real repository for now.
1015
In the long term getting this back from the remote repository as part
1016
of open would be more efficient.
1019
return self._real_repository._fetch_order
1022
def _fetch_uses_deltas(self):
1023
"""Decorate the real repository for now.
1025
In the long term getting this back from the remote repository as part
1026
of open would be more efficient.
1029
return self._real_repository._fetch_uses_deltas
1032
def _fetch_reconcile(self):
1033
"""Decorate the real repository for now.
1035
In the long term getting this back from the remote repository as part
1036
of open would be more efficient.
1039
return self._real_repository._fetch_reconcile
1041
def get_parent_map(self, revision_ids):
1042
"""See bzrlib.Graph.get_parent_map()."""
1043
return self._make_parents_provider().get_parent_map(revision_ids)
1045
def _get_parent_map_rpc(self, keys):
1046
"""Helper for get_parent_map that performs the RPC."""
1047
medium = self._client._medium
1048
if medium._is_remote_before((1, 2)):
1049
# We already found out that the server can't understand
1050
# Repository.get_parent_map requests, so just fetch the whole
1052
# XXX: Note that this will issue a deprecation warning. This is ok
1053
# :- its because we're working with a deprecated server anyway, and
1054
# the user will almost certainly have seen a warning about the
1055
# server version already.
1056
rg = self.get_revision_graph()
1057
# There is an api discrepency between get_parent_map and
1058
# get_revision_graph. Specifically, a "key:()" pair in
1059
# get_revision_graph just means a node has no parents. For
1060
# "get_parent_map" it means the node is a ghost. So fix up the
1061
# graph to correct this.
1062
# https://bugs.launchpad.net/bzr/+bug/214894
1063
# There is one other "bug" which is that ghosts in
1064
# get_revision_graph() are not returned at all. But we won't worry
1065
# about that for now.
1066
for node_id, parent_ids in rg.iteritems():
1067
if parent_ids == ():
1068
rg[node_id] = (NULL_REVISION,)
1069
rg[NULL_REVISION] = ()
1074
raise ValueError('get_parent_map(None) is not valid')
1075
if NULL_REVISION in keys:
1076
keys.discard(NULL_REVISION)
1077
found_parents = {NULL_REVISION:()}
1079
return found_parents
1082
# TODO(Needs analysis): We could assume that the keys being requested
1083
# from get_parent_map are in a breadth first search, so typically they
1084
# will all be depth N from some common parent, and we don't have to
1085
# have the server iterate from the root parent, but rather from the
1086
# keys we're searching; and just tell the server the keyspace we
1087
# already have; but this may be more traffic again.
1089
# Transform self._parents_map into a search request recipe.
1090
# TODO: Manage this incrementally to avoid covering the same path
1091
# repeatedly. (The server will have to on each request, but the less
1092
# work done the better).
1093
parents_map = self._unstacked_provider.get_cached_map()
1094
if parents_map is None:
1095
# Repository is not locked, so there's no cache.
1097
start_set = set(parents_map)
1098
result_parents = set()
1099
for parents in parents_map.itervalues():
1100
result_parents.update(parents)
1101
stop_keys = result_parents.difference(start_set)
1102
included_keys = start_set.intersection(result_parents)
1103
start_set.difference_update(included_keys)
1104
recipe = (start_set, stop_keys, len(parents_map))
1105
body = self._serialise_search_recipe(recipe)
1106
path = self.bzrdir._path_for_remote_call(self._client)
1108
if type(key) is not str:
1110
"key %r not a plain string" % (key,))
1111
verb = 'Repository.get_parent_map'
1112
args = (path,) + tuple(keys)
1114
response = self._call_with_body_bytes_expecting_body(
1116
except errors.UnknownSmartMethod:
1117
# Server does not support this method, so get the whole graph.
1118
# Worse, we have to force a disconnection, because the server now
1119
# doesn't realise it has a body on the wire to consume, so the
1120
# only way to recover is to abandon the connection.
1122
'Server is too old for fast get_parent_map, reconnecting. '
1123
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1125
# To avoid having to disconnect repeatedly, we keep track of the
1126
# fact the server doesn't understand remote methods added in 1.2.
1127
medium._remember_remote_is_before((1, 2))
1128
return self.get_revision_graph(None)
1129
response_tuple, response_handler = response
1130
if response_tuple[0] not in ['ok']:
1131
response_handler.cancel_read_body()
1132
raise errors.UnexpectedSmartServerResponse(response_tuple)
1133
if response_tuple[0] == 'ok':
1134
coded = bz2.decompress(response_handler.read_body_bytes())
1136
# no revisions found
1138
lines = coded.split('\n')
1141
d = tuple(line.split())
1143
revision_graph[d[0]] = d[1:]
1145
# No parents - so give the Graph result (NULL_REVISION,).
1146
revision_graph[d[0]] = (NULL_REVISION,)
1147
return revision_graph
1150
def get_signature_text(self, revision_id):
1152
return self._real_repository.get_signature_text(revision_id)
1155
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1156
def get_revision_graph_with_ghosts(self, revision_ids=None):
1158
return self._real_repository.get_revision_graph_with_ghosts(
1159
revision_ids=revision_ids)
1162
def get_inventory_xml(self, revision_id):
1164
return self._real_repository.get_inventory_xml(revision_id)
1166
def deserialise_inventory(self, revision_id, xml):
1168
return self._real_repository.deserialise_inventory(revision_id, xml)
1170
def reconcile(self, other=None, thorough=False):
1172
return self._real_repository.reconcile(other=other, thorough=thorough)
1174
def all_revision_ids(self):
1176
return self._real_repository.all_revision_ids()
1179
def get_deltas_for_revisions(self, revisions):
1181
return self._real_repository.get_deltas_for_revisions(revisions)
1184
def get_revision_delta(self, revision_id):
1186
return self._real_repository.get_revision_delta(revision_id)
1189
def revision_trees(self, revision_ids):
1191
return self._real_repository.revision_trees(revision_ids)
1194
def get_revision_reconcile(self, revision_id):
1196
return self._real_repository.get_revision_reconcile(revision_id)
1199
def check(self, revision_ids=None):
1201
return self._real_repository.check(revision_ids=revision_ids)
1203
def copy_content_into(self, destination, revision_id=None):
1205
return self._real_repository.copy_content_into(
1206
destination, revision_id=revision_id)
1208
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1209
# get a tarball of the remote repository, and copy from that into the
1211
from bzrlib import osutils
1213
# TODO: Maybe a progress bar while streaming the tarball?
1214
note("Copying repository content as tarball...")
1215
tar_file = self._get_tarball('bz2')
1216
if tar_file is None:
1218
destination = to_bzrdir.create_repository()
1220
tar = tarfile.open('repository', fileobj=tar_file,
1222
tmpdir = osutils.mkdtemp()
1224
_extract_tar(tar, tmpdir)
1225
tmp_bzrdir = BzrDir.open(tmpdir)
1226
tmp_repo = tmp_bzrdir.open_repository()
1227
tmp_repo.copy_content_into(destination, revision_id)
1229
osutils.rmtree(tmpdir)
1233
# TODO: Suggestion from john: using external tar is much faster than
1234
# python's tarfile library, but it may not work on windows.
1237
def inventories(self):
1238
"""Decorate the real repository for now.
1240
In the long term a full blown network facility is needed to
1241
avoid creating a real repository object locally.
1244
return self._real_repository.inventories
1248
"""Compress the data within the repository.
1250
This is not currently implemented within the smart server.
1253
return self._real_repository.pack()
1256
def revisions(self):
1257
"""Decorate the real repository for now.
1259
In the short term this should become a real object to intercept graph
1262
In the long term a full blown network facility is needed.
1265
return self._real_repository.revisions
1267
def set_make_working_trees(self, new_value):
1269
new_value_str = "True"
1271
new_value_str = "False"
1272
path = self.bzrdir._path_for_remote_call(self._client)
1274
response = self._call(
1275
'Repository.set_make_working_trees', path, new_value_str)
1276
except errors.UnknownSmartMethod:
1278
self._real_repository.set_make_working_trees(new_value)
1280
if response[0] != 'ok':
1281
raise errors.UnexpectedSmartServerResponse(response)
1284
def signatures(self):
1285
"""Decorate the real repository for now.
1287
In the long term a full blown network facility is needed to avoid
1288
creating a real repository object locally.
1291
return self._real_repository.signatures
1294
def sign_revision(self, revision_id, gpg_strategy):
1296
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1300
"""Decorate the real repository for now.
1302
In the long term a full blown network facility is needed to avoid
1303
creating a real repository object locally.
1306
return self._real_repository.texts
1309
def get_revisions(self, revision_ids):
1311
return self._real_repository.get_revisions(revision_ids)
1313
def supports_rich_root(self):
1315
return self._real_repository.supports_rich_root()
1317
def iter_reverse_revision_history(self, revision_id):
1319
return self._real_repository.iter_reverse_revision_history(revision_id)
1322
def _serializer(self):
1324
return self._real_repository._serializer
1326
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1328
return self._real_repository.store_revision_signature(
1329
gpg_strategy, plaintext, revision_id)
1331
def add_signature_text(self, revision_id, signature):
1333
return self._real_repository.add_signature_text(revision_id, signature)
1335
def has_signature_for_revision_id(self, revision_id):
1337
return self._real_repository.has_signature_for_revision_id(revision_id)
1339
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1341
return self._real_repository.item_keys_introduced_by(revision_ids,
1342
_files_pb=_files_pb)
1344
def revision_graph_can_have_wrong_parents(self):
1345
# The answer depends on the remote repo format.
1347
return self._real_repository.revision_graph_can_have_wrong_parents()
1349
def _find_inconsistent_revision_parents(self):
1351
return self._real_repository._find_inconsistent_revision_parents()
1353
def _check_for_inconsistent_revision_parents(self):
1355
return self._real_repository._check_for_inconsistent_revision_parents()
1357
def _make_parents_provider(self, other=None):
1358
providers = [self._unstacked_provider]
1359
if other is not None:
1360
providers.insert(0, other)
1361
providers.extend(r._make_parents_provider() for r in
1362
self._fallback_repositories)
1363
return graph._StackedParentsProvider(providers)
1365
def _serialise_search_recipe(self, recipe):
1366
"""Serialise a graph search recipe.
1368
:param recipe: A search recipe (start, stop, count).
1369
:return: Serialised bytes.
1371
start_keys = ' '.join(recipe[0])
1372
stop_keys = ' '.join(recipe[1])
1373
count = str(recipe[2])
1374
return '\n'.join((start_keys, stop_keys, count))
1377
path = self.bzrdir._path_for_remote_call(self._client)
1379
response = self._call('PackRepository.autopack', path)
1380
except errors.UnknownSmartMethod:
1382
self._real_repository._pack_collection.autopack()
1384
if self._real_repository is not None:
1385
# Reset the real repository's cache of pack names.
1386
# XXX: At some point we may be able to skip this and just rely on
1387
# the automatic retry logic to do the right thing, but for now we
1388
# err on the side of being correct rather than being optimal.
1389
self._real_repository._pack_collection.reload_pack_names()
1390
if response[0] != 'ok':
1391
raise errors.UnexpectedSmartServerResponse(response)
1394
class RemoteStreamSink(repository.StreamSink):
1396
def __init__(self, target_repo):
1397
repository.StreamSink.__init__(self, target_repo)
1399
def _insert_real(self, stream, src_format, resume_tokens):
1400
self.target_repo._ensure_real()
1401
sink = self.target_repo._real_repository._get_sink()
1402
result = sink.insert_stream(stream, src_format, resume_tokens)
1404
self.target_repo.autopack()
1407
def insert_stream(self, stream, src_format, resume_tokens):
1408
repo = self.target_repo
1409
client = repo._client
1410
medium = client._medium
1411
if medium._is_remote_before((1, 13)):
1412
# No possible way this can work.
1413
return self._insert_real(stream, src_format, resume_tokens)
1414
path = repo.bzrdir._path_for_remote_call(client)
1415
if not resume_tokens:
1416
# XXX: Ugly but important for correctness, *will* be fixed during
1417
# 1.13 cycle. Pushing a stream that is interrupted results in a
1418
# fallback to the _real_repositories sink *with a partial stream*.
1419
# Thats bad because we insert less data than bzr expected. To avoid
1420
# this we do a trial push to make sure the verb is accessible, and
1421
# do not fallback when actually pushing the stream. A cleanup patch
1422
# is going to look at rewinding/restarting the stream/partial
1424
byte_stream = self._stream_to_byte_stream([], src_format)
1426
response = client.call_with_body_stream(
1427
('Repository.insert_stream', path, ''), byte_stream)
1428
except errors.UnknownSmartMethod:
1429
medium._remember_remote_is_before((1,13))
1430
return self._insert_real(stream, src_format, resume_tokens)
1431
byte_stream = self._stream_to_byte_stream(stream, src_format)
1432
resume_tokens = ' '.join(resume_tokens)
1433
response = client.call_with_body_stream(
1434
('Repository.insert_stream', path, resume_tokens), byte_stream)
1435
if response[0][0] not in ('ok', 'missing-basis'):
1436
raise errors.UnexpectedSmartServerResponse(response)
1437
if response[0][0] == 'missing-basis':
1438
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1439
resume_tokens = tokens
1440
return resume_tokens, missing_keys
1442
if self.target_repo._real_repository is not None:
1443
collection = getattr(self.target_repo._real_repository,
1444
'_pack_collection', None)
1445
if collection is not None:
1446
collection.reload_pack_names()
1449
def _stream_to_byte_stream(self, stream, src_format):
1451
pack_writer = pack.ContainerWriter(bytes.append)
1453
pack_writer.add_bytes_record(src_format.network_name(), '')
1455
def get_adapter(adapter_key):
1457
return adapters[adapter_key]
1459
adapter_factory = adapter_registry.get(adapter_key)
1460
adapter = adapter_factory(self)
1461
adapters[adapter_key] = adapter
1463
for substream_type, substream in stream:
1464
for record in substream:
1465
if record.storage_kind in ('chunked', 'fulltext'):
1466
serialised = record_to_fulltext_bytes(record)
1468
serialised = record.get_bytes_as(record.storage_kind)
1469
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1478
class RemoteBranchLockableFiles(LockableFiles):
1479
"""A 'LockableFiles' implementation that talks to a smart server.
1481
This is not a public interface class.
1484
def __init__(self, bzrdir, _client):
1485
self.bzrdir = bzrdir
1486
self._client = _client
1487
self._need_find_modes = True
1488
LockableFiles.__init__(
1489
self, bzrdir.get_branch_transport(None),
1490
'lock', lockdir.LockDir)
1492
def _find_modes(self):
1493
# RemoteBranches don't let the client set the mode of control files.
1494
self._dir_mode = None
1495
self._file_mode = None
1498
class RemoteBranchFormat(branch.BranchFormat):
1501
super(RemoteBranchFormat, self).__init__()
1502
self._matchingbzrdir = RemoteBzrDirFormat()
1503
self._matchingbzrdir.set_branch_format(self)
1504
self._custom_format = None
1506
def __eq__(self, other):
1507
return (isinstance(other, RemoteBranchFormat) and
1508
self.__dict__ == other.__dict__)
1510
def get_format_description(self):
1511
return 'Remote BZR Branch'
1513
def network_name(self):
1514
return self._network_name
1516
def open(self, a_bzrdir):
1517
return a_bzrdir.open_branch()
1519
def _vfs_initialize(self, a_bzrdir):
1520
# Initialisation when using a local bzrdir object, or a non-vfs init
1521
# method is not available on the server.
1522
# self._custom_format is always set - the start of initialize ensures
1524
if isinstance(a_bzrdir, RemoteBzrDir):
1525
a_bzrdir._ensure_real()
1526
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1528
# We assume the bzrdir is parameterised; it may not be.
1529
result = self._custom_format.initialize(a_bzrdir)
1530
if (isinstance(a_bzrdir, RemoteBzrDir) and
1531
not isinstance(result, RemoteBranch)):
1532
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1535
def initialize(self, a_bzrdir):
1536
# 1) get the network name to use.
1537
if self._custom_format:
1538
network_name = self._custom_format.network_name()
1540
# Select the current bzrlib default and ask for that.
1541
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1542
reference_format = reference_bzrdir_format.get_branch_format()
1543
self._custom_format = reference_format
1544
network_name = reference_format.network_name()
1545
# Being asked to create on a non RemoteBzrDir:
1546
if not isinstance(a_bzrdir, RemoteBzrDir):
1547
return self._vfs_initialize(a_bzrdir)
1548
medium = a_bzrdir._client._medium
1549
if medium._is_remote_before((1, 13)):
1550
return self._vfs_initialize(a_bzrdir)
1551
# Creating on a remote bzr dir.
1552
# 2) try direct creation via RPC
1553
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1554
verb = 'BzrDir.create_branch'
1556
response = a_bzrdir._call(verb, path, network_name)
1557
except errors.UnknownSmartMethod:
1558
# Fallback - use vfs methods
1559
return self._vfs_initialize(a_bzrdir)
1560
if response[0] != 'ok':
1561
raise errors.UnexpectedSmartServerResponse(response)
1562
# Turn the response into a RemoteRepository object.
1563
format = RemoteBranchFormat()
1564
format._network_name = response[1]
1565
repo_format = response_tuple_to_repo_format(response[3:])
1566
if response[2] == '':
1567
repo_bzrdir = a_bzrdir
1569
repo_bzrdir = RemoteBzrDir(
1570
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1572
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1573
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1574
format=format, setup_stacking=False)
1575
# XXX: We know this is a new branch, so it must have revno 0, revid
1576
# NULL_REVISION. Creating the branch locked would make this be unable
1577
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1578
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1579
return remote_branch
1581
def supports_tags(self):
1582
# Remote branches might support tags, but we won't know until we
1583
# access the real remote branch.
1587
class RemoteBranch(branch.Branch, _RpcHelper):
1588
"""Branch stored on a server accessed by HPSS RPC.
1590
At the moment most operations are mapped down to simple file operations.
1593
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1594
_client=None, format=None, setup_stacking=True):
1595
"""Create a RemoteBranch instance.
1597
:param real_branch: An optional local implementation of the branch
1598
format, usually accessing the data via the VFS.
1599
:param _client: Private parameter for testing.
1600
:param format: A RemoteBranchFormat object, None to create one
1601
automatically. If supplied it should have a network_name already
1603
:param setup_stacking: If True make an RPC call to determine the
1604
stacked (or not) status of the branch. If False assume the branch
1607
# We intentionally don't call the parent class's __init__, because it
1608
# will try to assign to self.tags, which is a property in this subclass.
1609
# And the parent's __init__ doesn't do much anyway.
1610
self._revision_id_to_revno_cache = None
1611
self._partial_revision_id_to_revno_cache = {}
1612
self._revision_history_cache = None
1613
self._last_revision_info_cache = None
1614
self._merge_sorted_revisions_cache = None
1615
self.bzrdir = remote_bzrdir
1616
if _client is not None:
1617
self._client = _client
1619
self._client = remote_bzrdir._client
1620
self.repository = remote_repository
1621
if real_branch is not None:
1622
self._real_branch = real_branch
1623
# Give the remote repository the matching real repo.
1624
real_repo = self._real_branch.repository
1625
if isinstance(real_repo, RemoteRepository):
1626
real_repo._ensure_real()
1627
real_repo = real_repo._real_repository
1628
self.repository._set_real_repository(real_repo)
1629
# Give the branch the remote repository to let fast-pathing happen.
1630
self._real_branch.repository = self.repository
1632
self._real_branch = None
1633
# Fill out expected attributes of branch for bzrlib api users.
1634
self.base = self.bzrdir.root_transport.base
1635
self._control_files = None
1636
self._lock_mode = None
1637
self._lock_token = None
1638
self._repo_lock_token = None
1639
self._lock_count = 0
1640
self._leave_lock = False
1641
# Setup a format: note that we cannot call _ensure_real until all the
1642
# attributes above are set: This code cannot be moved higher up in this
1645
self._format = RemoteBranchFormat()
1646
if real_branch is not None:
1647
self._format._network_name = \
1648
self._real_branch._format.network_name()
1650
# # XXX: Need to get this from BzrDir.open_branch's return value.
1651
# self._ensure_real()
1652
# self._format._network_name = \
1653
# self._real_branch._format.network_name()
1655
self._format = format
1656
# The base class init is not called, so we duplicate this:
1657
hooks = branch.Branch.hooks['open']
1661
self._setup_stacking()
1663
def _setup_stacking(self):
1664
# configure stacking into the remote repository, by reading it from
1667
fallback_url = self.get_stacked_on_url()
1668
except (errors.NotStacked, errors.UnstackableBranchFormat,
1669
errors.UnstackableRepositoryFormat), e:
1671
# it's relative to this branch...
1672
fallback_url = urlutils.join(self.base, fallback_url)
1673
transports = [self.bzrdir.root_transport]
1674
if self._real_branch is not None:
1675
# The real repository is setup already:
1676
transports.append(self._real_branch._transport)
1677
self.repository.add_fallback_repository(
1678
self.repository._real_repository._fallback_repositories[0])
1680
stacked_on = branch.Branch.open(fallback_url,
1681
possible_transports=transports)
1682
self.repository.add_fallback_repository(stacked_on.repository)
1684
def _get_real_transport(self):
1685
# if we try vfs access, return the real branch's vfs transport
1687
return self._real_branch._transport
1689
_transport = property(_get_real_transport)
1692
return "%s(%s)" % (self.__class__.__name__, self.base)
1696
def _ensure_real(self):
1697
"""Ensure that there is a _real_branch set.
1699
Used before calls to self._real_branch.
1701
if self._real_branch is None:
1702
if not vfs.vfs_enabled():
1703
raise AssertionError('smart server vfs must be enabled '
1704
'to use vfs implementation')
1705
self.bzrdir._ensure_real()
1706
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1707
if self.repository._real_repository is None:
1708
# Give the remote repository the matching real repo.
1709
real_repo = self._real_branch.repository
1710
if isinstance(real_repo, RemoteRepository):
1711
real_repo._ensure_real()
1712
real_repo = real_repo._real_repository
1713
self.repository._set_real_repository(real_repo)
1714
# Give the real branch the remote repository to let fast-pathing
1716
self._real_branch.repository = self.repository
1717
if self._lock_mode == 'r':
1718
self._real_branch.lock_read()
1719
elif self._lock_mode == 'w':
1720
self._real_branch.lock_write(token=self._lock_token)
1722
def _translate_error(self, err, **context):
1723
self.repository._translate_error(err, branch=self, **context)
1725
def _clear_cached_state(self):
1726
super(RemoteBranch, self)._clear_cached_state()
1727
if self._real_branch is not None:
1728
self._real_branch._clear_cached_state()
1730
def _clear_cached_state_of_remote_branch_only(self):
1731
"""Like _clear_cached_state, but doesn't clear the cache of
1734
This is useful when falling back to calling a method of
1735
self._real_branch that changes state. In that case the underlying
1736
branch changes, so we need to invalidate this RemoteBranch's cache of
1737
it. However, there's no need to invalidate the _real_branch's cache
1738
too, in fact doing so might harm performance.
1740
super(RemoteBranch, self)._clear_cached_state()
1743
def control_files(self):
1744
# Defer actually creating RemoteBranchLockableFiles until its needed,
1745
# because it triggers an _ensure_real that we otherwise might not need.
1746
if self._control_files is None:
1747
self._control_files = RemoteBranchLockableFiles(
1748
self.bzrdir, self._client)
1749
return self._control_files
1751
def _get_checkout_format(self):
1753
return self._real_branch._get_checkout_format()
1755
def get_physical_lock_status(self):
1756
"""See Branch.get_physical_lock_status()."""
1757
# should be an API call to the server, as branches must be lockable.
1759
return self._real_branch.get_physical_lock_status()
1761
def get_stacked_on_url(self):
1762
"""Get the URL this branch is stacked against.
1764
:raises NotStacked: If the branch is not stacked.
1765
:raises UnstackableBranchFormat: If the branch does not support
1767
:raises UnstackableRepositoryFormat: If the repository does not support
1771
# there may not be a repository yet, so we can't use
1772
# self._translate_error, so we can't use self._call either.
1773
response = self._client.call('Branch.get_stacked_on_url',
1774
self._remote_path())
1775
except errors.ErrorFromSmartServer, err:
1776
# there may not be a repository yet, so we can't call through
1777
# its _translate_error
1778
_translate_error(err, branch=self)
1779
except errors.UnknownSmartMethod, err:
1781
return self._real_branch.get_stacked_on_url()
1782
if response[0] != 'ok':
1783
raise errors.UnexpectedSmartServerResponse(response)
1786
def lock_read(self):
1787
self.repository.lock_read()
1788
if not self._lock_mode:
1789
self._lock_mode = 'r'
1790
self._lock_count = 1
1791
if self._real_branch is not None:
1792
self._real_branch.lock_read()
1794
self._lock_count += 1
1796
def _remote_lock_write(self, token):
1798
branch_token = repo_token = ''
1800
branch_token = token
1801
repo_token = self.repository.lock_write()
1802
self.repository.unlock()
1803
err_context = {'token': token}
1804
response = self._call(
1805
'Branch.lock_write', self._remote_path(), branch_token,
1806
repo_token or '', **err_context)
1807
if response[0] != 'ok':
1808
raise errors.UnexpectedSmartServerResponse(response)
1809
ok, branch_token, repo_token = response
1810
return branch_token, repo_token
1812
def lock_write(self, token=None):
1813
if not self._lock_mode:
1814
# Lock the branch and repo in one remote call.
1815
remote_tokens = self._remote_lock_write(token)
1816
self._lock_token, self._repo_lock_token = remote_tokens
1817
if not self._lock_token:
1818
raise SmartProtocolError('Remote server did not return a token!')
1819
# Tell the self.repository object that it is locked.
1820
self.repository.lock_write(
1821
self._repo_lock_token, _skip_rpc=True)
1823
if self._real_branch is not None:
1824
self._real_branch.lock_write(token=self._lock_token)
1825
if token is not None:
1826
self._leave_lock = True
1828
self._leave_lock = False
1829
self._lock_mode = 'w'
1830
self._lock_count = 1
1831
elif self._lock_mode == 'r':
1832
raise errors.ReadOnlyTransaction
1834
if token is not None:
1835
# A token was given to lock_write, and we're relocking, so
1836
# check that the given token actually matches the one we
1838
if token != self._lock_token:
1839
raise errors.TokenMismatch(token, self._lock_token)
1840
self._lock_count += 1
1841
# Re-lock the repository too.
1842
self.repository.lock_write(self._repo_lock_token)
1843
return self._lock_token or None
1845
def _unlock(self, branch_token, repo_token):
1846
err_context = {'token': str((branch_token, repo_token))}
1847
response = self._call(
1848
'Branch.unlock', self._remote_path(), branch_token,
1849
repo_token or '', **err_context)
1850
if response == ('ok',):
1852
raise errors.UnexpectedSmartServerResponse(response)
1856
self._lock_count -= 1
1857
if not self._lock_count:
1858
self._clear_cached_state()
1859
mode = self._lock_mode
1860
self._lock_mode = None
1861
if self._real_branch is not None:
1862
if (not self._leave_lock and mode == 'w' and
1863
self._repo_lock_token):
1864
# If this RemoteBranch will remove the physical lock
1865
# for the repository, make sure the _real_branch
1866
# doesn't do it first. (Because the _real_branch's
1867
# repository is set to be the RemoteRepository.)
1868
self._real_branch.repository.leave_lock_in_place()
1869
self._real_branch.unlock()
1871
# Only write-locked branched need to make a remote method
1872
# call to perfom the unlock.
1874
if not self._lock_token:
1875
raise AssertionError('Locked, but no token!')
1876
branch_token = self._lock_token
1877
repo_token = self._repo_lock_token
1878
self._lock_token = None
1879
self._repo_lock_token = None
1880
if not self._leave_lock:
1881
self._unlock(branch_token, repo_token)
1883
self.repository.unlock()
1885
def break_lock(self):
1887
return self._real_branch.break_lock()
1889
def leave_lock_in_place(self):
1890
if not self._lock_token:
1891
raise NotImplementedError(self.leave_lock_in_place)
1892
self._leave_lock = True
1894
def dont_leave_lock_in_place(self):
1895
if not self._lock_token:
1896
raise NotImplementedError(self.dont_leave_lock_in_place)
1897
self._leave_lock = False
1899
def _last_revision_info(self):
1900
response = self._call('Branch.last_revision_info', self._remote_path())
1901
if response[0] != 'ok':
1902
raise SmartProtocolError('unexpected response code %s' % (response,))
1903
revno = int(response[1])
1904
last_revision = response[2]
1905
return (revno, last_revision)
1907
def _gen_revision_history(self):
1908
"""See Branch._gen_revision_history()."""
1909
response_tuple, response_handler = self._call_expecting_body(
1910
'Branch.revision_history', self._remote_path())
1911
if response_tuple[0] != 'ok':
1912
raise errors.UnexpectedSmartServerResponse(response_tuple)
1913
result = response_handler.read_body_bytes().split('\x00')
1918
def _remote_path(self):
1919
return self.bzrdir._path_for_remote_call(self._client)
1921
def _set_last_revision_descendant(self, revision_id, other_branch,
1922
allow_diverged=False, allow_overwrite_descendant=False):
1923
# This performs additional work to meet the hook contract; while its
1924
# undesirable, we have to synthesise the revno to call the hook, and
1925
# not calling the hook is worse as it means changes can't be prevented.
1926
# Having calculated this though, we can't just call into
1927
# set_last_revision_info as a simple call, because there is a set_rh
1928
# hook that some folk may still be using.
1929
old_revno, old_revid = self.last_revision_info()
1930
history = self._lefthand_history(revision_id)
1931
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1932
err_context = {'other_branch': other_branch}
1933
response = self._call('Branch.set_last_revision_ex',
1934
self._remote_path(), self._lock_token, self._repo_lock_token,
1935
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1937
self._clear_cached_state()
1938
if len(response) != 3 and response[0] != 'ok':
1939
raise errors.UnexpectedSmartServerResponse(response)
1940
new_revno, new_revision_id = response[1:]
1941
self._last_revision_info_cache = new_revno, new_revision_id
1942
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1943
if self._real_branch is not None:
1944
cache = new_revno, new_revision_id
1945
self._real_branch._last_revision_info_cache = cache
1947
def _set_last_revision(self, revision_id):
1948
old_revno, old_revid = self.last_revision_info()
1949
# This performs additional work to meet the hook contract; while its
1950
# undesirable, we have to synthesise the revno to call the hook, and
1951
# not calling the hook is worse as it means changes can't be prevented.
1952
# Having calculated this though, we can't just call into
1953
# set_last_revision_info as a simple call, because there is a set_rh
1954
# hook that some folk may still be using.
1955
history = self._lefthand_history(revision_id)
1956
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1957
self._clear_cached_state()
1958
response = self._call('Branch.set_last_revision',
1959
self._remote_path(), self._lock_token, self._repo_lock_token,
1961
if response != ('ok',):
1962
raise errors.UnexpectedSmartServerResponse(response)
1963
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1966
def set_revision_history(self, rev_history):
1967
# Send just the tip revision of the history; the server will generate
1968
# the full history from that. If the revision doesn't exist in this
1969
# branch, NoSuchRevision will be raised.
1970
if rev_history == []:
1973
rev_id = rev_history[-1]
1974
self._set_last_revision(rev_id)
1975
for hook in branch.Branch.hooks['set_rh']:
1976
hook(self, rev_history)
1977
self._cache_revision_history(rev_history)
1979
def get_parent(self):
1981
return self._real_branch.get_parent()
1983
def _get_parent_location(self):
1984
# Used by tests, when checking normalisation of given vs stored paths.
1986
return self._real_branch._get_parent_location()
1988
def set_parent(self, url):
1990
return self._real_branch.set_parent(url)
1992
def _set_parent_location(self, url):
1993
# Used by tests, to poke bad urls into branch configurations
1995
self.set_parent(url)
1998
return self._real_branch._set_parent_location(url)
2000
def set_stacked_on_url(self, stacked_location):
2001
"""Set the URL this branch is stacked against.
2003
:raises UnstackableBranchFormat: If the branch does not support
2005
:raises UnstackableRepositoryFormat: If the repository does not support
2009
return self._real_branch.set_stacked_on_url(stacked_location)
2012
def pull(self, source, overwrite=False, stop_revision=None,
2014
self._clear_cached_state_of_remote_branch_only()
2016
return self._real_branch.pull(
2017
source, overwrite=overwrite, stop_revision=stop_revision,
2018
_override_hook_target=self, **kwargs)
2021
def push(self, target, overwrite=False, stop_revision=None):
2023
return self._real_branch.push(
2024
target, overwrite=overwrite, stop_revision=stop_revision,
2025
_override_hook_source_branch=self)
2027
def is_locked(self):
2028
return self._lock_count >= 1
2031
def revision_id_to_revno(self, revision_id):
2033
return self._real_branch.revision_id_to_revno(revision_id)
2036
def set_last_revision_info(self, revno, revision_id):
2037
# XXX: These should be returned by the set_last_revision_info verb
2038
old_revno, old_revid = self.last_revision_info()
2039
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2040
revision_id = ensure_null(revision_id)
2042
response = self._call('Branch.set_last_revision_info',
2043
self._remote_path(), self._lock_token, self._repo_lock_token,
2044
str(revno), revision_id)
2045
except errors.UnknownSmartMethod:
2047
self._clear_cached_state_of_remote_branch_only()
2048
self._real_branch.set_last_revision_info(revno, revision_id)
2049
self._last_revision_info_cache = revno, revision_id
2051
if response == ('ok',):
2052
self._clear_cached_state()
2053
self._last_revision_info_cache = revno, revision_id
2054
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2055
# Update the _real_branch's cache too.
2056
if self._real_branch is not None:
2057
cache = self._last_revision_info_cache
2058
self._real_branch._last_revision_info_cache = cache
2060
raise errors.UnexpectedSmartServerResponse(response)
2063
def generate_revision_history(self, revision_id, last_rev=None,
2065
medium = self._client._medium
2066
if not medium._is_remote_before((1, 6)):
2067
# Use a smart method for 1.6 and above servers
2069
self._set_last_revision_descendant(revision_id, other_branch,
2070
allow_diverged=True, allow_overwrite_descendant=True)
2072
except errors.UnknownSmartMethod:
2073
medium._remember_remote_is_before((1, 6))
2074
self._clear_cached_state_of_remote_branch_only()
2075
self.set_revision_history(self._lefthand_history(revision_id,
2076
last_rev=last_rev,other_branch=other_branch))
2081
return self._real_branch.tags
2083
def set_push_location(self, location):
2085
return self._real_branch.set_push_location(location)
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)