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, repository as smart_repo
44
from bzrlib.revision import ensure_null, NULL_REVISION
45
from bzrlib.trace import mutter, note, warning
46
from bzrlib.util import bencode
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
73
def response_tuple_to_repo_format(response):
74
"""Convert a response tuple describing a repository format to a format."""
75
format = RemoteRepositoryFormat()
76
format.rich_root_data = (response[0] == 'yes')
77
format.supports_tree_reference = (response[1] == 'yes')
78
format.supports_external_lookups = (response[2] == 'yes')
79
format._network_name = response[3]
83
# Note: RemoteBzrDirFormat is in bzrdir.py
85
class RemoteBzrDir(BzrDir, _RpcHelper):
86
"""Control directory on a remote server, accessed via bzr:// or similar."""
88
def __init__(self, transport, format, _client=None):
89
"""Construct a RemoteBzrDir.
91
:param _client: Private parameter for testing. Disables probing and the
94
BzrDir.__init__(self, transport, format)
95
# this object holds a delegated bzrdir that uses file-level operations
96
# to talk to the other side
97
self._real_bzrdir = None
98
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
# create_branch for details.
100
self._next_open_branch_result = None
103
medium = transport.get_smart_medium()
104
self._client = client._SmartClient(medium)
106
self._client = _client
109
path = self._path_for_remote_call(self._client)
110
response = self._call('BzrDir.open', path)
111
if response not in [('yes',), ('no',)]:
112
raise errors.UnexpectedSmartServerResponse(response)
113
if response == ('no',):
114
raise errors.NotBranchError(path=transport.base)
116
def _ensure_real(self):
117
"""Ensure that there is a _real_bzrdir set.
119
Used before calls to self._real_bzrdir.
121
if not self._real_bzrdir:
122
self._real_bzrdir = BzrDir.open_from_transport(
123
self.root_transport, _server_formats=False)
124
self._format._network_name = \
125
self._real_bzrdir._format.network_name()
127
def _translate_error(self, err, **context):
128
_translate_error(err, bzrdir=self, **context)
130
def break_lock(self):
131
# Prevent aliasing problems in the next_open_branch_result cache.
132
# See create_branch for rationale.
133
self._next_open_branch_result = None
134
return BzrDir.break_lock(self)
136
def _vfs_cloning_metadir(self, require_stacking=False):
138
return self._real_bzrdir.cloning_metadir(
139
require_stacking=require_stacking)
141
def cloning_metadir(self, require_stacking=False):
142
medium = self._client._medium
143
if medium._is_remote_before((1, 13)):
144
return self._vfs_cloning_metadir(require_stacking=require_stacking)
145
verb = 'BzrDir.cloning_metadir'
150
path = self._path_for_remote_call(self._client)
152
response = self._call(verb, path, stacking)
153
except errors.UnknownSmartMethod:
154
return self._vfs_cloning_metadir(require_stacking=require_stacking)
155
if len(response) != 3:
156
raise errors.UnexpectedSmartServerResponse(response)
157
control_name, repo_name, branch_name = response
158
format = bzrdir.network_format_registry.get(control_name)
160
format.repository_format = repository.network_format_registry.get(
163
format.set_branch_format(
164
branch.network_format_registry.get(branch_name))
167
def create_repository(self, shared=False):
168
# as per meta1 formats - just delegate to the format object which may
170
result = self._format.repository_format.initialize(self, shared)
171
if not isinstance(result, RemoteRepository):
172
return self.open_repository()
176
def destroy_repository(self):
177
"""See BzrDir.destroy_repository"""
179
self._real_bzrdir.destroy_repository()
181
def create_branch(self):
182
# as per meta1 formats - just delegate to the format object which may
184
real_branch = self._format.get_branch_format().initialize(self)
185
if not isinstance(real_branch, RemoteBranch):
186
result = RemoteBranch(self, self.find_repository(), real_branch)
189
# BzrDir.clone_on_transport() uses the result of create_branch but does
190
# not return it to its callers; we save approximately 8% of our round
191
# trips by handing the branch we created back to the first caller to
192
# open_branch rather than probing anew. Long term we need a API in
193
# bzrdir that doesn't discard result objects (like result_branch).
195
self._next_open_branch_result = result
198
def destroy_branch(self):
199
"""See BzrDir.destroy_branch"""
201
self._real_bzrdir.destroy_branch()
202
self._next_open_branch_result = None
204
def create_workingtree(self, revision_id=None, from_branch=None):
205
raise errors.NotLocalUrl(self.transport.base)
207
def find_branch_format(self):
208
"""Find the branch 'format' for this bzrdir.
210
This might be a synthetic object for e.g. RemoteBranch and SVN.
212
b = self.open_branch()
215
def get_branch_reference(self):
216
"""See BzrDir.get_branch_reference()."""
217
path = self._path_for_remote_call(self._client)
218
response = self._call('BzrDir.open_branch', path)
219
if response[0] == 'ok':
220
if response[1] == '':
221
# branch at this location.
224
# a branch reference, use the existing BranchReference logic.
227
raise errors.UnexpectedSmartServerResponse(response)
229
def _get_tree_branch(self):
230
"""See BzrDir._get_tree_branch()."""
231
return None, self.open_branch()
233
def open_branch(self, _unsupported=False):
235
raise NotImplementedError('unsupported flag support not implemented yet.')
236
if self._next_open_branch_result is not None:
237
# See create_branch for details.
238
result = self._next_open_branch_result
239
self._next_open_branch_result = None
241
reference_url = self.get_branch_reference()
242
if reference_url is None:
243
# branch at this location.
244
return RemoteBranch(self, self.find_repository())
246
# a branch reference, use the existing BranchReference logic.
247
format = BranchReferenceFormat()
248
return format.open(self, _found=True, location=reference_url)
250
def _open_repo_v1(self, path):
251
verb = 'BzrDir.find_repository'
252
response = self._call(verb, path)
253
if response[0] != 'ok':
254
raise errors.UnexpectedSmartServerResponse(response)
255
# servers that only support the v1 method don't support external
258
repo = self._real_bzrdir.open_repository()
259
response = response + ('no', repo._format.network_name())
260
return response, repo
262
def _open_repo_v2(self, path):
263
verb = 'BzrDir.find_repositoryV2'
264
response = self._call(verb, path)
265
if response[0] != 'ok':
266
raise errors.UnexpectedSmartServerResponse(response)
268
repo = self._real_bzrdir.open_repository()
269
response = response + (repo._format.network_name(),)
270
return response, repo
272
def _open_repo_v3(self, path):
273
verb = 'BzrDir.find_repositoryV3'
274
medium = self._client._medium
275
if medium._is_remote_before((1, 13)):
276
raise errors.UnknownSmartMethod(verb)
277
response = self._call(verb, path)
278
if response[0] != 'ok':
279
raise errors.UnexpectedSmartServerResponse(response)
280
return response, None
282
def open_repository(self):
283
path = self._path_for_remote_call(self._client)
285
for probe in [self._open_repo_v3, self._open_repo_v2,
288
response, real_repo = probe(path)
290
except errors.UnknownSmartMethod:
293
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
294
if response[0] != 'ok':
295
raise errors.UnexpectedSmartServerResponse(response)
296
if len(response) != 6:
297
raise SmartProtocolError('incorrect response length %s' % (response,))
298
if response[1] == '':
299
# repo is at this dir.
300
format = response_tuple_to_repo_format(response[2:])
301
# Used to support creating a real format instance when needed.
302
format._creating_bzrdir = self
303
remote_repo = RemoteRepository(self, format)
304
format._creating_repo = remote_repo
305
if real_repo is not None:
306
remote_repo._set_real_repository(real_repo)
309
raise errors.NoRepositoryPresent(self)
311
def open_workingtree(self, recommend_upgrade=True):
313
if self._real_bzrdir.has_workingtree():
314
raise errors.NotLocalUrl(self.root_transport)
316
raise errors.NoWorkingTree(self.root_transport.base)
318
def _path_for_remote_call(self, client):
319
"""Return the path to be used for this bzrdir in a remote call."""
320
return client.remote_path_from_transport(self.root_transport)
322
def get_branch_transport(self, branch_format):
324
return self._real_bzrdir.get_branch_transport(branch_format)
326
def get_repository_transport(self, repository_format):
328
return self._real_bzrdir.get_repository_transport(repository_format)
330
def get_workingtree_transport(self, workingtree_format):
332
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
334
def can_convert_format(self):
335
"""Upgrading of remote bzrdirs is not supported yet."""
338
def needs_format_conversion(self, format=None):
339
"""Upgrading of remote bzrdirs is not supported yet."""
341
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
342
% 'needs_format_conversion(format=None)')
345
def clone(self, url, revision_id=None, force_new_repo=False,
346
preserve_stacking=False):
348
return self._real_bzrdir.clone(url, revision_id=revision_id,
349
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
351
def get_config(self):
353
return self._real_bzrdir.get_config()
356
class RemoteRepositoryFormat(repository.RepositoryFormat):
357
"""Format for repositories accessed over a _SmartClient.
359
Instances of this repository are represented by RemoteRepository
362
The RemoteRepositoryFormat is parameterized during construction
363
to reflect the capabilities of the real, remote format. Specifically
364
the attributes rich_root_data and supports_tree_reference are set
365
on a per instance basis, and are not set (and should not be) at
368
:ivar _custom_format: If set, a specific concrete repository format that
369
will be used when initializing a repository with this
370
RemoteRepositoryFormat.
371
:ivar _creating_repo: If set, the repository object that this
372
RemoteRepositoryFormat was created for: it can be called into
373
to obtain data like the network name.
376
_matchingbzrdir = RemoteBzrDirFormat()
379
repository.RepositoryFormat.__init__(self)
380
self._custom_format = None
381
self._network_name = None
382
self._creating_bzrdir = None
384
def _vfs_initialize(self, a_bzrdir, shared):
385
"""Helper for common code in initialize."""
386
if self._custom_format:
387
# Custom format requested
388
result = self._custom_format.initialize(a_bzrdir, shared=shared)
389
elif self._creating_bzrdir is not None:
390
# Use the format that the repository we were created to back
392
prior_repo = self._creating_bzrdir.open_repository()
393
prior_repo._ensure_real()
394
result = prior_repo._real_repository._format.initialize(
395
a_bzrdir, shared=shared)
397
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
398
# support remote initialization.
399
# We delegate to a real object at this point (as RemoteBzrDir
400
# delegate to the repository format which would lead to infinite
401
# recursion if we just called a_bzrdir.create_repository.
402
a_bzrdir._ensure_real()
403
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
404
if not isinstance(result, RemoteRepository):
405
return self.open(a_bzrdir)
409
def initialize(self, a_bzrdir, shared=False):
410
# Being asked to create on a non RemoteBzrDir:
411
if not isinstance(a_bzrdir, RemoteBzrDir):
412
return self._vfs_initialize(a_bzrdir, shared)
413
medium = a_bzrdir._client._medium
414
if medium._is_remote_before((1, 13)):
415
return self._vfs_initialize(a_bzrdir, shared)
416
# Creating on a remote bzr dir.
417
# 1) get the network name to use.
418
if self._custom_format:
419
network_name = self._custom_format.network_name()
421
# Select the current bzrlib default and ask for that.
422
reference_bzrdir_format = bzrdir.format_registry.get('default')()
423
reference_format = reference_bzrdir_format.repository_format
424
network_name = reference_format.network_name()
425
# 2) try direct creation via RPC
426
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
427
verb = 'BzrDir.create_repository'
433
response = a_bzrdir._call(verb, path, network_name, shared_str)
434
except errors.UnknownSmartMethod:
435
# Fallback - use vfs methods
436
return self._vfs_initialize(a_bzrdir, shared)
438
# Turn the response into a RemoteRepository object.
439
format = response_tuple_to_repo_format(response[1:])
440
# Used to support creating a real format instance when needed.
441
format._creating_bzrdir = a_bzrdir
442
remote_repo = RemoteRepository(a_bzrdir, format)
443
format._creating_repo = remote_repo
446
def open(self, a_bzrdir):
447
if not isinstance(a_bzrdir, RemoteBzrDir):
448
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
449
return a_bzrdir.open_repository()
451
def _ensure_real(self):
452
if self._custom_format is None:
453
self._custom_format = repository.network_format_registry.get(
457
def _fetch_order(self):
459
return self._custom_format._fetch_order
462
def _fetch_uses_deltas(self):
464
return self._custom_format._fetch_uses_deltas
467
def _fetch_reconcile(self):
469
return self._custom_format._fetch_reconcile
471
def get_format_description(self):
472
return 'bzr remote repository'
474
def __eq__(self, other):
475
return self.__class__ == other.__class__
477
def check_conversion_target(self, target_format):
478
if self.rich_root_data and not target_format.rich_root_data:
479
raise errors.BadConversionTarget(
480
'Does not support rich root data.', target_format)
481
if (self.supports_tree_reference and
482
not getattr(target_format, 'supports_tree_reference', False)):
483
raise errors.BadConversionTarget(
484
'Does not support nested trees', target_format)
486
def network_name(self):
487
if self._network_name:
488
return self._network_name
489
self._creating_repo._ensure_real()
490
return self._creating_repo._real_repository._format.network_name()
493
def _serializer(self):
495
return self._custom_format._serializer
498
class RemoteRepository(_RpcHelper):
499
"""Repository accessed over rpc.
501
For the moment most operations are performed using local transport-backed
505
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
506
"""Create a RemoteRepository instance.
508
:param remote_bzrdir: The bzrdir hosting this repository.
509
:param format: The RemoteFormat object to use.
510
:param real_repository: If not None, a local implementation of the
511
repository logic for the repository, usually accessing the data
513
:param _client: Private testing parameter - override the smart client
514
to be used by the repository.
517
self._real_repository = real_repository
519
self._real_repository = None
520
self.bzrdir = remote_bzrdir
522
self._client = remote_bzrdir._client
524
self._client = _client
525
self._format = format
526
self._lock_mode = None
527
self._lock_token = None
529
self._leave_lock = False
530
self._unstacked_provider = graph.CachingParentsProvider(
531
get_parent_map=self._get_parent_map_rpc)
532
self._unstacked_provider.disable_cache()
534
# These depend on the actual remote format, so force them off for
535
# maximum compatibility. XXX: In future these should depend on the
536
# remote repository instance, but this is irrelevant until we perform
537
# reconcile via an RPC call.
538
self._reconcile_does_inventory_gc = False
539
self._reconcile_fixes_text_parents = False
540
self._reconcile_backsup_inventory = False
541
self.base = self.bzrdir.transport.base
542
# Additional places to query for data.
543
self._fallback_repositories = []
546
return "%s(%s)" % (self.__class__.__name__, self.base)
550
def abort_write_group(self, suppress_errors=False):
551
"""Complete a write group on the decorated repository.
553
Smart methods peform operations in a single step so this api
554
is not really applicable except as a compatibility thunk
555
for older plugins that don't use e.g. the CommitBuilder
558
:param suppress_errors: see Repository.abort_write_group.
561
return self._real_repository.abort_write_group(
562
suppress_errors=suppress_errors)
564
def commit_write_group(self):
565
"""Complete a write group on the decorated repository.
567
Smart methods peform operations in a single step so this api
568
is not really applicable except as a compatibility thunk
569
for older plugins that don't use e.g. the CommitBuilder
573
return self._real_repository.commit_write_group()
575
def resume_write_group(self, tokens):
577
return self._real_repository.resume_write_group(tokens)
579
def suspend_write_group(self):
581
return self._real_repository.suspend_write_group()
583
def _ensure_real(self):
584
"""Ensure that there is a _real_repository set.
586
Used before calls to self._real_repository.
588
if self._real_repository is None:
589
self.bzrdir._ensure_real()
590
self._set_real_repository(
591
self.bzrdir._real_bzrdir.open_repository())
593
def _translate_error(self, err, **context):
594
self.bzrdir._translate_error(err, repository=self, **context)
596
def find_text_key_references(self):
597
"""Find the text key references within the repository.
599
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
600
revision_ids. Each altered file-ids has the exact revision_ids that
601
altered it listed explicitly.
602
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
603
to whether they were referred to by the inventory of the
604
revision_id that they contain. The inventory texts from all present
605
revision ids are assessed to generate this report.
608
return self._real_repository.find_text_key_references()
610
def _generate_text_key_index(self):
611
"""Generate a new text key index for the repository.
613
This is an expensive function that will take considerable time to run.
615
:return: A dict mapping (file_id, revision_id) tuples to a list of
616
parents, also (file_id, revision_id) tuples.
619
return self._real_repository._generate_text_key_index()
621
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
622
def get_revision_graph(self, revision_id=None):
623
"""See Repository.get_revision_graph()."""
624
return self._get_revision_graph(revision_id)
626
def _get_revision_graph(self, revision_id):
627
"""Private method for using with old (< 1.2) servers to fallback."""
628
if revision_id is None:
630
elif revision.is_null(revision_id):
633
path = self.bzrdir._path_for_remote_call(self._client)
634
response = self._call_expecting_body(
635
'Repository.get_revision_graph', path, revision_id)
636
response_tuple, response_handler = response
637
if response_tuple[0] != 'ok':
638
raise errors.UnexpectedSmartServerResponse(response_tuple)
639
coded = response_handler.read_body_bytes()
641
# no revisions in this repository!
643
lines = coded.split('\n')
646
d = tuple(line.split())
647
revision_graph[d[0]] = d[1:]
649
return revision_graph
652
"""See Repository._get_sink()."""
653
return RemoteStreamSink(self)
655
def _get_source(self, to_format):
656
"""Return a source for streaming from this repository."""
657
return RemoteStreamSource(self, to_format)
659
def has_revision(self, revision_id):
660
"""See Repository.has_revision()."""
661
if revision_id == NULL_REVISION:
662
# The null revision is always present.
664
path = self.bzrdir._path_for_remote_call(self._client)
665
response = self._call('Repository.has_revision', path, revision_id)
666
if response[0] not in ('yes', 'no'):
667
raise errors.UnexpectedSmartServerResponse(response)
668
if response[0] == 'yes':
670
for fallback_repo in self._fallback_repositories:
671
if fallback_repo.has_revision(revision_id):
675
def has_revisions(self, revision_ids):
676
"""See Repository.has_revisions()."""
677
# FIXME: This does many roundtrips, particularly when there are
678
# fallback repositories. -- mbp 20080905
680
for revision_id in revision_ids:
681
if self.has_revision(revision_id):
682
result.add(revision_id)
685
def has_same_location(self, other):
686
return (self.__class__ == other.__class__ and
687
self.bzrdir.transport.base == other.bzrdir.transport.base)
689
def get_graph(self, other_repository=None):
690
"""Return the graph for this repository format"""
691
parents_provider = self._make_parents_provider(other_repository)
692
return graph.Graph(parents_provider)
694
def gather_stats(self, revid=None, committers=None):
695
"""See Repository.gather_stats()."""
696
path = self.bzrdir._path_for_remote_call(self._client)
697
# revid can be None to indicate no revisions, not just NULL_REVISION
698
if revid is None or revision.is_null(revid):
702
if committers is None or not committers:
703
fmt_committers = 'no'
705
fmt_committers = 'yes'
706
response_tuple, response_handler = self._call_expecting_body(
707
'Repository.gather_stats', path, fmt_revid, fmt_committers)
708
if response_tuple[0] != 'ok':
709
raise errors.UnexpectedSmartServerResponse(response_tuple)
711
body = response_handler.read_body_bytes()
713
for line in body.split('\n'):
716
key, val_text = line.split(':')
717
if key in ('revisions', 'size', 'committers'):
718
result[key] = int(val_text)
719
elif key in ('firstrev', 'latestrev'):
720
values = val_text.split(' ')[1:]
721
result[key] = (float(values[0]), long(values[1]))
725
def find_branches(self, using=False):
726
"""See Repository.find_branches()."""
727
# should be an API call to the server.
729
return self._real_repository.find_branches(using=using)
731
def get_physical_lock_status(self):
732
"""See Repository.get_physical_lock_status()."""
733
# should be an API call to the server.
735
return self._real_repository.get_physical_lock_status()
737
def is_in_write_group(self):
738
"""Return True if there is an open write group.
740
write groups are only applicable locally for the smart server..
742
if self._real_repository:
743
return self._real_repository.is_in_write_group()
746
return self._lock_count >= 1
749
"""See Repository.is_shared()."""
750
path = self.bzrdir._path_for_remote_call(self._client)
751
response = self._call('Repository.is_shared', path)
752
if response[0] not in ('yes', 'no'):
753
raise SmartProtocolError('unexpected response code %s' % (response,))
754
return response[0] == 'yes'
756
def is_write_locked(self):
757
return self._lock_mode == 'w'
760
# wrong eventually - want a local lock cache context
761
if not self._lock_mode:
762
self._lock_mode = 'r'
764
self._unstacked_provider.enable_cache(cache_misses=False)
765
if self._real_repository is not None:
766
self._real_repository.lock_read()
768
self._lock_count += 1
770
def _remote_lock_write(self, token):
771
path = self.bzrdir._path_for_remote_call(self._client)
774
err_context = {'token': token}
775
response = self._call('Repository.lock_write', path, token,
777
if response[0] == 'ok':
781
raise errors.UnexpectedSmartServerResponse(response)
783
def lock_write(self, token=None, _skip_rpc=False):
784
if not self._lock_mode:
786
if self._lock_token is not None:
787
if token != self._lock_token:
788
raise errors.TokenMismatch(token, self._lock_token)
789
self._lock_token = token
791
self._lock_token = self._remote_lock_write(token)
792
# if self._lock_token is None, then this is something like packs or
793
# svn where we don't get to lock the repo, or a weave style repository
794
# where we cannot lock it over the wire and attempts to do so will
796
if self._real_repository is not None:
797
self._real_repository.lock_write(token=self._lock_token)
798
if token is not None:
799
self._leave_lock = True
801
self._leave_lock = False
802
self._lock_mode = 'w'
804
self._unstacked_provider.enable_cache(cache_misses=False)
805
elif self._lock_mode == 'r':
806
raise errors.ReadOnlyError(self)
808
self._lock_count += 1
809
return self._lock_token or None
811
def leave_lock_in_place(self):
812
if not self._lock_token:
813
raise NotImplementedError(self.leave_lock_in_place)
814
self._leave_lock = True
816
def dont_leave_lock_in_place(self):
817
if not self._lock_token:
818
raise NotImplementedError(self.dont_leave_lock_in_place)
819
self._leave_lock = False
821
def _set_real_repository(self, repository):
822
"""Set the _real_repository for this repository.
824
:param repository: The repository to fallback to for non-hpss
825
implemented operations.
827
if self._real_repository is not None:
828
# Replacing an already set real repository.
829
# We cannot do this [currently] if the repository is locked -
830
# synchronised state might be lost.
832
raise AssertionError('_real_repository is already set')
833
if isinstance(repository, RemoteRepository):
834
raise AssertionError()
835
self._real_repository = repository
836
for fb in self._fallback_repositories:
837
self._real_repository.add_fallback_repository(fb)
838
if self._lock_mode == 'w':
839
# if we are already locked, the real repository must be able to
840
# acquire the lock with our token.
841
self._real_repository.lock_write(self._lock_token)
842
elif self._lock_mode == 'r':
843
self._real_repository.lock_read()
845
def start_write_group(self):
846
"""Start a write group on the decorated repository.
848
Smart methods peform operations in a single step so this api
849
is not really applicable except as a compatibility thunk
850
for older plugins that don't use e.g. the CommitBuilder
854
return self._real_repository.start_write_group()
856
def _unlock(self, token):
857
path = self.bzrdir._path_for_remote_call(self._client)
859
# with no token the remote repository is not persistently locked.
861
err_context = {'token': token}
862
response = self._call('Repository.unlock', path, token,
864
if response == ('ok',):
867
raise errors.UnexpectedSmartServerResponse(response)
870
if not self._lock_count:
871
raise errors.LockNotHeld(self)
872
self._lock_count -= 1
873
if self._lock_count > 0:
875
self._unstacked_provider.disable_cache()
876
old_mode = self._lock_mode
877
self._lock_mode = None
879
# The real repository is responsible at present for raising an
880
# exception if it's in an unfinished write group. However, it
881
# normally will *not* actually remove the lock from disk - that's
882
# done by the server on receiving the Repository.unlock call.
883
# This is just to let the _real_repository stay up to date.
884
if self._real_repository is not None:
885
self._real_repository.unlock()
887
# The rpc-level lock should be released even if there was a
888
# problem releasing the vfs-based lock.
890
# Only write-locked repositories need to make a remote method
891
# call to perfom the unlock.
892
old_token = self._lock_token
893
self._lock_token = None
894
if not self._leave_lock:
895
self._unlock(old_token)
897
def break_lock(self):
898
# should hand off to the network
900
return self._real_repository.break_lock()
902
def _get_tarball(self, compression):
903
"""Return a TemporaryFile containing a repository tarball.
905
Returns None if the server does not support sending tarballs.
908
path = self.bzrdir._path_for_remote_call(self._client)
910
response, protocol = self._call_expecting_body(
911
'Repository.tarball', path, compression)
912
except errors.UnknownSmartMethod:
913
protocol.cancel_read_body()
915
if response[0] == 'ok':
916
# Extract the tarball and return it
917
t = tempfile.NamedTemporaryFile()
918
# TODO: rpc layer should read directly into it...
919
t.write(protocol.read_body_bytes())
922
raise errors.UnexpectedSmartServerResponse(response)
924
def sprout(self, to_bzrdir, revision_id=None):
925
# TODO: Option to control what format is created?
927
dest_repo = self._real_repository._format.initialize(to_bzrdir,
929
dest_repo.fetch(self, revision_id=revision_id)
932
### These methods are just thin shims to the VFS object for now.
934
def revision_tree(self, revision_id):
936
return self._real_repository.revision_tree(revision_id)
938
def get_serializer_format(self):
940
return self._real_repository.get_serializer_format()
942
def get_commit_builder(self, branch, parents, config, timestamp=None,
943
timezone=None, committer=None, revprops=None,
945
# FIXME: It ought to be possible to call this without immediately
946
# triggering _ensure_real. For now it's the easiest thing to do.
948
real_repo = self._real_repository
949
builder = real_repo.get_commit_builder(branch, parents,
950
config, timestamp=timestamp, timezone=timezone,
951
committer=committer, revprops=revprops, revision_id=revision_id)
954
def add_fallback_repository(self, repository):
955
"""Add a repository to use for looking up data not held locally.
957
:param repository: A repository.
959
# XXX: At the moment the RemoteRepository will allow fallbacks
960
# unconditionally - however, a _real_repository will usually exist,
961
# and may raise an error if it's not accommodated by the underlying
962
# format. Eventually we should check when opening the repository
963
# whether it's willing to allow them or not.
965
# We need to accumulate additional repositories here, to pass them in
968
self._fallback_repositories.append(repository)
969
# If self._real_repository was parameterised already (e.g. because a
970
# _real_branch had its get_stacked_on_url method called), then the
971
# repository to be added may already be in the _real_repositories list.
972
if self._real_repository is not None:
973
if repository not in self._real_repository._fallback_repositories:
974
self._real_repository.add_fallback_repository(repository)
976
# They are also seen by the fallback repository. If it doesn't
977
# exist yet they'll be added then. This implicitly copies them.
980
def add_inventory(self, revid, inv, parents):
982
return self._real_repository.add_inventory(revid, inv, parents)
984
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
987
return self._real_repository.add_inventory_by_delta(basis_revision_id,
988
delta, new_revision_id, parents)
990
def add_revision(self, rev_id, rev, inv=None, config=None):
992
return self._real_repository.add_revision(
993
rev_id, rev, inv=inv, config=config)
996
def get_inventory(self, revision_id):
998
return self._real_repository.get_inventory(revision_id)
1000
def iter_inventories(self, revision_ids):
1002
return self._real_repository.iter_inventories(revision_ids)
1005
def get_revision(self, revision_id):
1007
return self._real_repository.get_revision(revision_id)
1009
def get_transaction(self):
1011
return self._real_repository.get_transaction()
1014
def clone(self, a_bzrdir, revision_id=None):
1016
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1018
def make_working_trees(self):
1019
"""See Repository.make_working_trees"""
1021
return self._real_repository.make_working_trees()
1023
def revision_ids_to_search_result(self, result_set):
1024
"""Convert a set of revision ids to a graph SearchResult."""
1025
result_parents = set()
1026
for parents in self.get_graph().get_parent_map(
1027
result_set).itervalues():
1028
result_parents.update(parents)
1029
included_keys = result_set.intersection(result_parents)
1030
start_keys = result_set.difference(included_keys)
1031
exclude_keys = result_parents.difference(result_set)
1032
result = graph.SearchResult(start_keys, exclude_keys,
1033
len(result_set), result_set)
1037
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1038
"""Return the revision ids that other has that this does not.
1040
These are returned in topological order.
1042
revision_id: only return revision ids included by revision_id.
1044
return repository.InterRepository.get(
1045
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1047
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
1048
# Not delegated to _real_repository so that InterRepository.get has a
1049
# chance to find an InterRepository specialised for RemoteRepository.
1050
if self.has_same_location(source):
1051
# check that last_revision is in 'from' and then return a
1053
if (revision_id is not None and
1054
not revision.is_null(revision_id)):
1055
self.get_revision(revision_id)
1057
inter = repository.InterRepository.get(source, self)
1059
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
1060
except NotImplementedError:
1061
raise errors.IncompatibleRepositories(source, self)
1063
def create_bundle(self, target, base, fileobj, format=None):
1065
self._real_repository.create_bundle(target, base, fileobj, format)
1068
def get_ancestry(self, revision_id, topo_sorted=True):
1070
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1072
def fileids_altered_by_revision_ids(self, revision_ids):
1074
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1076
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1078
return self._real_repository._get_versioned_file_checker(
1079
revisions, revision_versions_cache)
1081
def iter_files_bytes(self, desired_files):
1082
"""See Repository.iter_file_bytes.
1085
return self._real_repository.iter_files_bytes(desired_files)
1087
def get_parent_map(self, revision_ids):
1088
"""See bzrlib.Graph.get_parent_map()."""
1089
return self._make_parents_provider().get_parent_map(revision_ids)
1091
def _get_parent_map_rpc(self, keys):
1092
"""Helper for get_parent_map that performs the RPC."""
1093
medium = self._client._medium
1094
if medium._is_remote_before((1, 2)):
1095
# We already found out that the server can't understand
1096
# Repository.get_parent_map requests, so just fetch the whole
1098
# XXX: Note that this will issue a deprecation warning. This is ok
1099
# :- its because we're working with a deprecated server anyway, and
1100
# the user will almost certainly have seen a warning about the
1101
# server version already.
1102
rg = self.get_revision_graph()
1103
# There is an api discrepency between get_parent_map and
1104
# get_revision_graph. Specifically, a "key:()" pair in
1105
# get_revision_graph just means a node has no parents. For
1106
# "get_parent_map" it means the node is a ghost. So fix up the
1107
# graph to correct this.
1108
# https://bugs.launchpad.net/bzr/+bug/214894
1109
# There is one other "bug" which is that ghosts in
1110
# get_revision_graph() are not returned at all. But we won't worry
1111
# about that for now.
1112
for node_id, parent_ids in rg.iteritems():
1113
if parent_ids == ():
1114
rg[node_id] = (NULL_REVISION,)
1115
rg[NULL_REVISION] = ()
1120
raise ValueError('get_parent_map(None) is not valid')
1121
if NULL_REVISION in keys:
1122
keys.discard(NULL_REVISION)
1123
found_parents = {NULL_REVISION:()}
1125
return found_parents
1128
# TODO(Needs analysis): We could assume that the keys being requested
1129
# from get_parent_map are in a breadth first search, so typically they
1130
# will all be depth N from some common parent, and we don't have to
1131
# have the server iterate from the root parent, but rather from the
1132
# keys we're searching; and just tell the server the keyspace we
1133
# already have; but this may be more traffic again.
1135
# Transform self._parents_map into a search request recipe.
1136
# TODO: Manage this incrementally to avoid covering the same path
1137
# repeatedly. (The server will have to on each request, but the less
1138
# work done the better).
1139
parents_map = self._unstacked_provider.get_cached_map()
1140
if parents_map is None:
1141
# Repository is not locked, so there's no cache.
1143
start_set = set(parents_map)
1144
result_parents = set()
1145
for parents in parents_map.itervalues():
1146
result_parents.update(parents)
1147
stop_keys = result_parents.difference(start_set)
1148
included_keys = start_set.intersection(result_parents)
1149
start_set.difference_update(included_keys)
1150
recipe = (start_set, stop_keys, len(parents_map))
1151
body = self._serialise_search_recipe(recipe)
1152
path = self.bzrdir._path_for_remote_call(self._client)
1154
if type(key) is not str:
1156
"key %r not a plain string" % (key,))
1157
verb = 'Repository.get_parent_map'
1158
args = (path,) + tuple(keys)
1160
response = self._call_with_body_bytes_expecting_body(
1162
except errors.UnknownSmartMethod:
1163
# Server does not support this method, so get the whole graph.
1164
# Worse, we have to force a disconnection, because the server now
1165
# doesn't realise it has a body on the wire to consume, so the
1166
# only way to recover is to abandon the connection.
1168
'Server is too old for fast get_parent_map, reconnecting. '
1169
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1171
# To avoid having to disconnect repeatedly, we keep track of the
1172
# fact the server doesn't understand remote methods added in 1.2.
1173
medium._remember_remote_is_before((1, 2))
1174
return self.get_revision_graph(None)
1175
response_tuple, response_handler = response
1176
if response_tuple[0] not in ['ok']:
1177
response_handler.cancel_read_body()
1178
raise errors.UnexpectedSmartServerResponse(response_tuple)
1179
if response_tuple[0] == 'ok':
1180
coded = bz2.decompress(response_handler.read_body_bytes())
1182
# no revisions found
1184
lines = coded.split('\n')
1187
d = tuple(line.split())
1189
revision_graph[d[0]] = d[1:]
1191
# No parents - so give the Graph result (NULL_REVISION,).
1192
revision_graph[d[0]] = (NULL_REVISION,)
1193
return revision_graph
1196
def get_signature_text(self, revision_id):
1198
return self._real_repository.get_signature_text(revision_id)
1201
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1202
def get_revision_graph_with_ghosts(self, revision_ids=None):
1204
return self._real_repository.get_revision_graph_with_ghosts(
1205
revision_ids=revision_ids)
1208
def get_inventory_xml(self, revision_id):
1210
return self._real_repository.get_inventory_xml(revision_id)
1212
def deserialise_inventory(self, revision_id, xml):
1214
return self._real_repository.deserialise_inventory(revision_id, xml)
1216
def reconcile(self, other=None, thorough=False):
1218
return self._real_repository.reconcile(other=other, thorough=thorough)
1220
def all_revision_ids(self):
1222
return self._real_repository.all_revision_ids()
1225
def get_deltas_for_revisions(self, revisions):
1227
return self._real_repository.get_deltas_for_revisions(revisions)
1230
def get_revision_delta(self, revision_id):
1232
return self._real_repository.get_revision_delta(revision_id)
1235
def revision_trees(self, revision_ids):
1237
return self._real_repository.revision_trees(revision_ids)
1240
def get_revision_reconcile(self, revision_id):
1242
return self._real_repository.get_revision_reconcile(revision_id)
1245
def check(self, revision_ids=None):
1247
return self._real_repository.check(revision_ids=revision_ids)
1249
def copy_content_into(self, destination, revision_id=None):
1251
return self._real_repository.copy_content_into(
1252
destination, revision_id=revision_id)
1254
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1255
# get a tarball of the remote repository, and copy from that into the
1257
from bzrlib import osutils
1259
# TODO: Maybe a progress bar while streaming the tarball?
1260
note("Copying repository content as tarball...")
1261
tar_file = self._get_tarball('bz2')
1262
if tar_file is None:
1264
destination = to_bzrdir.create_repository()
1266
tar = tarfile.open('repository', fileobj=tar_file,
1268
tmpdir = osutils.mkdtemp()
1270
_extract_tar(tar, tmpdir)
1271
tmp_bzrdir = BzrDir.open(tmpdir)
1272
tmp_repo = tmp_bzrdir.open_repository()
1273
tmp_repo.copy_content_into(destination, revision_id)
1275
osutils.rmtree(tmpdir)
1279
# TODO: Suggestion from john: using external tar is much faster than
1280
# python's tarfile library, but it may not work on windows.
1283
def inventories(self):
1284
"""Decorate the real repository for now.
1286
In the long term a full blown network facility is needed to
1287
avoid creating a real repository object locally.
1290
return self._real_repository.inventories
1294
"""Compress the data within the repository.
1296
This is not currently implemented within the smart server.
1299
return self._real_repository.pack()
1302
def revisions(self):
1303
"""Decorate the real repository for now.
1305
In the short term this should become a real object to intercept graph
1308
In the long term a full blown network facility is needed.
1311
return self._real_repository.revisions
1313
def set_make_working_trees(self, new_value):
1315
new_value_str = "True"
1317
new_value_str = "False"
1318
path = self.bzrdir._path_for_remote_call(self._client)
1320
response = self._call(
1321
'Repository.set_make_working_trees', path, new_value_str)
1322
except errors.UnknownSmartMethod:
1324
self._real_repository.set_make_working_trees(new_value)
1326
if response[0] != 'ok':
1327
raise errors.UnexpectedSmartServerResponse(response)
1330
def signatures(self):
1331
"""Decorate the real repository for now.
1333
In the long term a full blown network facility is needed to avoid
1334
creating a real repository object locally.
1337
return self._real_repository.signatures
1340
def sign_revision(self, revision_id, gpg_strategy):
1342
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1346
"""Decorate the real repository for now.
1348
In the long term a full blown network facility is needed to avoid
1349
creating a real repository object locally.
1352
return self._real_repository.texts
1355
def get_revisions(self, revision_ids):
1357
return self._real_repository.get_revisions(revision_ids)
1359
def supports_rich_root(self):
1360
return self._format.rich_root_data
1362
def iter_reverse_revision_history(self, revision_id):
1364
return self._real_repository.iter_reverse_revision_history(revision_id)
1367
def _serializer(self):
1368
return self._format._serializer
1370
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1372
return self._real_repository.store_revision_signature(
1373
gpg_strategy, plaintext, revision_id)
1375
def add_signature_text(self, revision_id, signature):
1377
return self._real_repository.add_signature_text(revision_id, signature)
1379
def has_signature_for_revision_id(self, revision_id):
1381
return self._real_repository.has_signature_for_revision_id(revision_id)
1383
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1385
return self._real_repository.item_keys_introduced_by(revision_ids,
1386
_files_pb=_files_pb)
1388
def revision_graph_can_have_wrong_parents(self):
1389
# The answer depends on the remote repo format.
1391
return self._real_repository.revision_graph_can_have_wrong_parents()
1393
def _find_inconsistent_revision_parents(self):
1395
return self._real_repository._find_inconsistent_revision_parents()
1397
def _check_for_inconsistent_revision_parents(self):
1399
return self._real_repository._check_for_inconsistent_revision_parents()
1401
def _make_parents_provider(self, other=None):
1402
providers = [self._unstacked_provider]
1403
if other is not None:
1404
providers.insert(0, other)
1405
providers.extend(r._make_parents_provider() for r in
1406
self._fallback_repositories)
1407
return graph._StackedParentsProvider(providers)
1409
def _serialise_search_recipe(self, recipe):
1410
"""Serialise a graph search recipe.
1412
:param recipe: A search recipe (start, stop, count).
1413
:return: Serialised bytes.
1415
start_keys = ' '.join(recipe[0])
1416
stop_keys = ' '.join(recipe[1])
1417
count = str(recipe[2])
1418
return '\n'.join((start_keys, stop_keys, count))
1421
path = self.bzrdir._path_for_remote_call(self._client)
1423
response = self._call('PackRepository.autopack', path)
1424
except errors.UnknownSmartMethod:
1426
self._real_repository._pack_collection.autopack()
1428
if self._real_repository is not None:
1429
# Reset the real repository's cache of pack names.
1430
# XXX: At some point we may be able to skip this and just rely on
1431
# the automatic retry logic to do the right thing, but for now we
1432
# err on the side of being correct rather than being optimal.
1433
self._real_repository._pack_collection.reload_pack_names()
1434
if response[0] != 'ok':
1435
raise errors.UnexpectedSmartServerResponse(response)
1438
class RemoteStreamSink(repository.StreamSink):
1440
def _insert_real(self, stream, src_format, resume_tokens):
1441
self.target_repo._ensure_real()
1442
sink = self.target_repo._real_repository._get_sink()
1443
result = sink.insert_stream(stream, src_format, resume_tokens)
1445
self.target_repo.autopack()
1448
def insert_stream(self, stream, src_format, resume_tokens):
1449
repo = self.target_repo
1450
client = repo._client
1451
medium = client._medium
1452
if medium._is_remote_before((1, 13)):
1453
# No possible way this can work.
1454
return self._insert_real(stream, src_format, resume_tokens)
1455
path = repo.bzrdir._path_for_remote_call(client)
1456
if not resume_tokens:
1457
# XXX: Ugly but important for correctness, *will* be fixed during
1458
# 1.13 cycle. Pushing a stream that is interrupted results in a
1459
# fallback to the _real_repositories sink *with a partial stream*.
1460
# Thats bad because we insert less data than bzr expected. To avoid
1461
# this we do a trial push to make sure the verb is accessible, and
1462
# do not fallback when actually pushing the stream. A cleanup patch
1463
# is going to look at rewinding/restarting the stream/partial
1465
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1467
response = client.call_with_body_stream(
1468
('Repository.insert_stream', path, ''), byte_stream)
1469
except errors.UnknownSmartMethod:
1470
medium._remember_remote_is_before((1,13))
1471
return self._insert_real(stream, src_format, resume_tokens)
1472
byte_stream = smart_repo._stream_to_byte_stream(
1474
resume_tokens = ' '.join(resume_tokens)
1475
response = client.call_with_body_stream(
1476
('Repository.insert_stream', path, resume_tokens), byte_stream)
1477
if response[0][0] not in ('ok', 'missing-basis'):
1478
raise errors.UnexpectedSmartServerResponse(response)
1479
if response[0][0] == 'missing-basis':
1480
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1481
resume_tokens = tokens
1482
return resume_tokens, missing_keys
1484
if self.target_repo._real_repository is not None:
1485
collection = getattr(self.target_repo._real_repository,
1486
'_pack_collection', None)
1487
if collection is not None:
1488
collection.reload_pack_names()
1492
class RemoteStreamSource(repository.StreamSource):
1493
"""Stream data from a remote server."""
1495
def get_stream(self, search):
1496
# streaming with fallback repositories is not well defined yet: The
1497
# remote repository cannot see the fallback repositories, and thus
1498
# cannot satisfy the entire search in the general case. Likewise the
1499
# fallback repositories cannot reify the search to determine what they
1500
# should send. It likely needs a return value in the stream listing the
1501
# edge of the search to resume from in fallback repositories.
1502
if self.from_repository._fallback_repositories:
1503
return repository.StreamSource.get_stream(self, search)
1504
repo = self.from_repository
1505
client = repo._client
1506
medium = client._medium
1507
if medium._is_remote_before((1, 13)):
1508
# No possible way this can work.
1509
return repository.StreamSource.get_stream(self, search)
1510
path = repo.bzrdir._path_for_remote_call(client)
1512
recipe = repo._serialise_search_recipe(search._recipe)
1513
response = repo._call_with_body_bytes_expecting_body(
1514
'Repository.get_stream',
1515
(path, self.to_format.network_name()), recipe)
1516
response_tuple, response_handler = response
1517
except errors.UnknownSmartMethod:
1518
medium._remember_remote_is_before((1,13))
1519
return repository.StreamSource.get_stream(self, search)
1520
if response_tuple[0] != 'ok':
1521
raise errors.UnexpectedSmartServerResponse(response_tuple)
1522
byte_stream = response_handler.read_streamed_body()
1523
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1524
if src_format.network_name() != repo._format.network_name():
1525
raise AssertionError(
1526
"Mismatched RemoteRepository and stream src %r, %r" % (
1527
src_format.network_name(), repo._format.network_name()))
1531
class RemoteBranchLockableFiles(LockableFiles):
1532
"""A 'LockableFiles' implementation that talks to a smart server.
1534
This is not a public interface class.
1537
def __init__(self, bzrdir, _client):
1538
self.bzrdir = bzrdir
1539
self._client = _client
1540
self._need_find_modes = True
1541
LockableFiles.__init__(
1542
self, bzrdir.get_branch_transport(None),
1543
'lock', lockdir.LockDir)
1545
def _find_modes(self):
1546
# RemoteBranches don't let the client set the mode of control files.
1547
self._dir_mode = None
1548
self._file_mode = None
1551
class RemoteBranchFormat(branch.BranchFormat):
1554
super(RemoteBranchFormat, self).__init__()
1555
self._matchingbzrdir = RemoteBzrDirFormat()
1556
self._matchingbzrdir.set_branch_format(self)
1557
self._custom_format = None
1559
def __eq__(self, other):
1560
return (isinstance(other, RemoteBranchFormat) and
1561
self.__dict__ == other.__dict__)
1563
def get_format_description(self):
1564
return 'Remote BZR Branch'
1566
def network_name(self):
1567
return self._network_name
1569
def open(self, a_bzrdir):
1570
return a_bzrdir.open_branch()
1572
def _vfs_initialize(self, a_bzrdir):
1573
# Initialisation when using a local bzrdir object, or a non-vfs init
1574
# method is not available on the server.
1575
# self._custom_format is always set - the start of initialize ensures
1577
if isinstance(a_bzrdir, RemoteBzrDir):
1578
a_bzrdir._ensure_real()
1579
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1581
# We assume the bzrdir is parameterised; it may not be.
1582
result = self._custom_format.initialize(a_bzrdir)
1583
if (isinstance(a_bzrdir, RemoteBzrDir) and
1584
not isinstance(result, RemoteBranch)):
1585
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1588
def initialize(self, a_bzrdir):
1589
# 1) get the network name to use.
1590
if self._custom_format:
1591
network_name = self._custom_format.network_name()
1593
# Select the current bzrlib default and ask for that.
1594
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1595
reference_format = reference_bzrdir_format.get_branch_format()
1596
self._custom_format = reference_format
1597
network_name = reference_format.network_name()
1598
# Being asked to create on a non RemoteBzrDir:
1599
if not isinstance(a_bzrdir, RemoteBzrDir):
1600
return self._vfs_initialize(a_bzrdir)
1601
medium = a_bzrdir._client._medium
1602
if medium._is_remote_before((1, 13)):
1603
return self._vfs_initialize(a_bzrdir)
1604
# Creating on a remote bzr dir.
1605
# 2) try direct creation via RPC
1606
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1607
verb = 'BzrDir.create_branch'
1609
response = a_bzrdir._call(verb, path, network_name)
1610
except errors.UnknownSmartMethod:
1611
# Fallback - use vfs methods
1612
return self._vfs_initialize(a_bzrdir)
1613
if response[0] != 'ok':
1614
raise errors.UnexpectedSmartServerResponse(response)
1615
# Turn the response into a RemoteRepository object.
1616
format = RemoteBranchFormat()
1617
format._network_name = response[1]
1618
repo_format = response_tuple_to_repo_format(response[3:])
1619
if response[2] == '':
1620
repo_bzrdir = a_bzrdir
1622
repo_bzrdir = RemoteBzrDir(
1623
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1625
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1626
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1627
format=format, setup_stacking=False)
1628
# XXX: We know this is a new branch, so it must have revno 0, revid
1629
# NULL_REVISION. Creating the branch locked would make this be unable
1630
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1631
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1632
return remote_branch
1634
def supports_tags(self):
1635
# Remote branches might support tags, but we won't know until we
1636
# access the real remote branch.
1640
class RemoteBranch(branch.Branch, _RpcHelper):
1641
"""Branch stored on a server accessed by HPSS RPC.
1643
At the moment most operations are mapped down to simple file operations.
1646
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1647
_client=None, format=None, setup_stacking=True):
1648
"""Create a RemoteBranch instance.
1650
:param real_branch: An optional local implementation of the branch
1651
format, usually accessing the data via the VFS.
1652
:param _client: Private parameter for testing.
1653
:param format: A RemoteBranchFormat object, None to create one
1654
automatically. If supplied it should have a network_name already
1656
:param setup_stacking: If True make an RPC call to determine the
1657
stacked (or not) status of the branch. If False assume the branch
1660
# We intentionally don't call the parent class's __init__, because it
1661
# will try to assign to self.tags, which is a property in this subclass.
1662
# And the parent's __init__ doesn't do much anyway.
1663
self._revision_id_to_revno_cache = None
1664
self._partial_revision_id_to_revno_cache = {}
1665
self._revision_history_cache = None
1666
self._last_revision_info_cache = None
1667
self._merge_sorted_revisions_cache = None
1668
self.bzrdir = remote_bzrdir
1669
if _client is not None:
1670
self._client = _client
1672
self._client = remote_bzrdir._client
1673
self.repository = remote_repository
1674
if real_branch is not None:
1675
self._real_branch = real_branch
1676
# Give the remote repository the matching real repo.
1677
real_repo = self._real_branch.repository
1678
if isinstance(real_repo, RemoteRepository):
1679
real_repo._ensure_real()
1680
real_repo = real_repo._real_repository
1681
self.repository._set_real_repository(real_repo)
1682
# Give the branch the remote repository to let fast-pathing happen.
1683
self._real_branch.repository = self.repository
1685
self._real_branch = None
1686
# Fill out expected attributes of branch for bzrlib api users.
1687
self.base = self.bzrdir.root_transport.base
1688
self._control_files = None
1689
self._lock_mode = None
1690
self._lock_token = None
1691
self._repo_lock_token = None
1692
self._lock_count = 0
1693
self._leave_lock = False
1694
# Setup a format: note that we cannot call _ensure_real until all the
1695
# attributes above are set: This code cannot be moved higher up in this
1698
self._format = RemoteBranchFormat()
1699
if real_branch is not None:
1700
self._format._network_name = \
1701
self._real_branch._format.network_name()
1703
# # XXX: Need to get this from BzrDir.open_branch's return value.
1704
# self._ensure_real()
1705
# self._format._network_name = \
1706
# self._real_branch._format.network_name()
1708
self._format = format
1709
# The base class init is not called, so we duplicate this:
1710
hooks = branch.Branch.hooks['open']
1714
self._setup_stacking()
1716
def _setup_stacking(self):
1717
# configure stacking into the remote repository, by reading it from
1720
fallback_url = self.get_stacked_on_url()
1721
except (errors.NotStacked, errors.UnstackableBranchFormat,
1722
errors.UnstackableRepositoryFormat), e:
1724
# it's relative to this branch...
1725
fallback_url = urlutils.join(self.base, fallback_url)
1726
transports = [self.bzrdir.root_transport]
1727
if self._real_branch is not None:
1728
# The real repository is setup already:
1729
transports.append(self._real_branch._transport)
1730
self.repository.add_fallback_repository(
1731
self.repository._real_repository._fallback_repositories[0])
1733
stacked_on = branch.Branch.open(fallback_url,
1734
possible_transports=transports)
1735
self.repository.add_fallback_repository(stacked_on.repository)
1737
def _get_real_transport(self):
1738
# if we try vfs access, return the real branch's vfs transport
1740
return self._real_branch._transport
1742
_transport = property(_get_real_transport)
1745
return "%s(%s)" % (self.__class__.__name__, self.base)
1749
def _ensure_real(self):
1750
"""Ensure that there is a _real_branch set.
1752
Used before calls to self._real_branch.
1754
if self._real_branch is None:
1755
if not vfs.vfs_enabled():
1756
raise AssertionError('smart server vfs must be enabled '
1757
'to use vfs implementation')
1758
self.bzrdir._ensure_real()
1759
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1760
if self.repository._real_repository is None:
1761
# Give the remote repository the matching real repo.
1762
real_repo = self._real_branch.repository
1763
if isinstance(real_repo, RemoteRepository):
1764
real_repo._ensure_real()
1765
real_repo = real_repo._real_repository
1766
self.repository._set_real_repository(real_repo)
1767
# Give the real branch the remote repository to let fast-pathing
1769
self._real_branch.repository = self.repository
1770
if self._lock_mode == 'r':
1771
self._real_branch.lock_read()
1772
elif self._lock_mode == 'w':
1773
self._real_branch.lock_write(token=self._lock_token)
1775
def _translate_error(self, err, **context):
1776
self.repository._translate_error(err, branch=self, **context)
1778
def _clear_cached_state(self):
1779
super(RemoteBranch, self)._clear_cached_state()
1780
if self._real_branch is not None:
1781
self._real_branch._clear_cached_state()
1783
def _clear_cached_state_of_remote_branch_only(self):
1784
"""Like _clear_cached_state, but doesn't clear the cache of
1787
This is useful when falling back to calling a method of
1788
self._real_branch that changes state. In that case the underlying
1789
branch changes, so we need to invalidate this RemoteBranch's cache of
1790
it. However, there's no need to invalidate the _real_branch's cache
1791
too, in fact doing so might harm performance.
1793
super(RemoteBranch, self)._clear_cached_state()
1796
def control_files(self):
1797
# Defer actually creating RemoteBranchLockableFiles until its needed,
1798
# because it triggers an _ensure_real that we otherwise might not need.
1799
if self._control_files is None:
1800
self._control_files = RemoteBranchLockableFiles(
1801
self.bzrdir, self._client)
1802
return self._control_files
1804
def _get_checkout_format(self):
1806
return self._real_branch._get_checkout_format()
1808
def get_physical_lock_status(self):
1809
"""See Branch.get_physical_lock_status()."""
1810
# should be an API call to the server, as branches must be lockable.
1812
return self._real_branch.get_physical_lock_status()
1814
def get_stacked_on_url(self):
1815
"""Get the URL this branch is stacked against.
1817
:raises NotStacked: If the branch is not stacked.
1818
:raises UnstackableBranchFormat: If the branch does not support
1820
:raises UnstackableRepositoryFormat: If the repository does not support
1824
# there may not be a repository yet, so we can't use
1825
# self._translate_error, so we can't use self._call either.
1826
response = self._client.call('Branch.get_stacked_on_url',
1827
self._remote_path())
1828
except errors.ErrorFromSmartServer, err:
1829
# there may not be a repository yet, so we can't call through
1830
# its _translate_error
1831
_translate_error(err, branch=self)
1832
except errors.UnknownSmartMethod, err:
1834
return self._real_branch.get_stacked_on_url()
1835
if response[0] != 'ok':
1836
raise errors.UnexpectedSmartServerResponse(response)
1839
def lock_read(self):
1840
self.repository.lock_read()
1841
if not self._lock_mode:
1842
self._lock_mode = 'r'
1843
self._lock_count = 1
1844
if self._real_branch is not None:
1845
self._real_branch.lock_read()
1847
self._lock_count += 1
1849
def _remote_lock_write(self, token):
1851
branch_token = repo_token = ''
1853
branch_token = token
1854
repo_token = self.repository.lock_write()
1855
self.repository.unlock()
1856
err_context = {'token': token}
1857
response = self._call(
1858
'Branch.lock_write', self._remote_path(), branch_token,
1859
repo_token or '', **err_context)
1860
if response[0] != 'ok':
1861
raise errors.UnexpectedSmartServerResponse(response)
1862
ok, branch_token, repo_token = response
1863
return branch_token, repo_token
1865
def lock_write(self, token=None):
1866
if not self._lock_mode:
1867
# Lock the branch and repo in one remote call.
1868
remote_tokens = self._remote_lock_write(token)
1869
self._lock_token, self._repo_lock_token = remote_tokens
1870
if not self._lock_token:
1871
raise SmartProtocolError('Remote server did not return a token!')
1872
# Tell the self.repository object that it is locked.
1873
self.repository.lock_write(
1874
self._repo_lock_token, _skip_rpc=True)
1876
if self._real_branch is not None:
1877
self._real_branch.lock_write(token=self._lock_token)
1878
if token is not None:
1879
self._leave_lock = True
1881
self._leave_lock = False
1882
self._lock_mode = 'w'
1883
self._lock_count = 1
1884
elif self._lock_mode == 'r':
1885
raise errors.ReadOnlyTransaction
1887
if token is not None:
1888
# A token was given to lock_write, and we're relocking, so
1889
# check that the given token actually matches the one we
1891
if token != self._lock_token:
1892
raise errors.TokenMismatch(token, self._lock_token)
1893
self._lock_count += 1
1894
# Re-lock the repository too.
1895
self.repository.lock_write(self._repo_lock_token)
1896
return self._lock_token or None
1898
def _unlock(self, branch_token, repo_token):
1899
err_context = {'token': str((branch_token, repo_token))}
1900
response = self._call(
1901
'Branch.unlock', self._remote_path(), branch_token,
1902
repo_token or '', **err_context)
1903
if response == ('ok',):
1905
raise errors.UnexpectedSmartServerResponse(response)
1909
self._lock_count -= 1
1910
if not self._lock_count:
1911
self._clear_cached_state()
1912
mode = self._lock_mode
1913
self._lock_mode = None
1914
if self._real_branch is not None:
1915
if (not self._leave_lock and mode == 'w' and
1916
self._repo_lock_token):
1917
# If this RemoteBranch will remove the physical lock
1918
# for the repository, make sure the _real_branch
1919
# doesn't do it first. (Because the _real_branch's
1920
# repository is set to be the RemoteRepository.)
1921
self._real_branch.repository.leave_lock_in_place()
1922
self._real_branch.unlock()
1924
# Only write-locked branched need to make a remote method
1925
# call to perfom the unlock.
1927
if not self._lock_token:
1928
raise AssertionError('Locked, but no token!')
1929
branch_token = self._lock_token
1930
repo_token = self._repo_lock_token
1931
self._lock_token = None
1932
self._repo_lock_token = None
1933
if not self._leave_lock:
1934
self._unlock(branch_token, repo_token)
1936
self.repository.unlock()
1938
def break_lock(self):
1940
return self._real_branch.break_lock()
1942
def leave_lock_in_place(self):
1943
if not self._lock_token:
1944
raise NotImplementedError(self.leave_lock_in_place)
1945
self._leave_lock = True
1947
def dont_leave_lock_in_place(self):
1948
if not self._lock_token:
1949
raise NotImplementedError(self.dont_leave_lock_in_place)
1950
self._leave_lock = False
1952
def _last_revision_info(self):
1953
response = self._call('Branch.last_revision_info', self._remote_path())
1954
if response[0] != 'ok':
1955
raise SmartProtocolError('unexpected response code %s' % (response,))
1956
revno = int(response[1])
1957
last_revision = response[2]
1958
return (revno, last_revision)
1960
def _gen_revision_history(self):
1961
"""See Branch._gen_revision_history()."""
1962
response_tuple, response_handler = self._call_expecting_body(
1963
'Branch.revision_history', self._remote_path())
1964
if response_tuple[0] != 'ok':
1965
raise errors.UnexpectedSmartServerResponse(response_tuple)
1966
result = response_handler.read_body_bytes().split('\x00')
1971
def _remote_path(self):
1972
return self.bzrdir._path_for_remote_call(self._client)
1974
def _set_last_revision_descendant(self, revision_id, other_branch,
1975
allow_diverged=False, allow_overwrite_descendant=False):
1976
# This performs additional work to meet the hook contract; while its
1977
# undesirable, we have to synthesise the revno to call the hook, and
1978
# not calling the hook is worse as it means changes can't be prevented.
1979
# Having calculated this though, we can't just call into
1980
# set_last_revision_info as a simple call, because there is a set_rh
1981
# hook that some folk may still be using.
1982
old_revno, old_revid = self.last_revision_info()
1983
history = self._lefthand_history(revision_id)
1984
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1985
err_context = {'other_branch': other_branch}
1986
response = self._call('Branch.set_last_revision_ex',
1987
self._remote_path(), self._lock_token, self._repo_lock_token,
1988
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1990
self._clear_cached_state()
1991
if len(response) != 3 and response[0] != 'ok':
1992
raise errors.UnexpectedSmartServerResponse(response)
1993
new_revno, new_revision_id = response[1:]
1994
self._last_revision_info_cache = new_revno, new_revision_id
1995
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1996
if self._real_branch is not None:
1997
cache = new_revno, new_revision_id
1998
self._real_branch._last_revision_info_cache = cache
2000
def _set_last_revision(self, revision_id):
2001
old_revno, old_revid = self.last_revision_info()
2002
# This performs additional work to meet the hook contract; while its
2003
# undesirable, we have to synthesise the revno to call the hook, and
2004
# not calling the hook is worse as it means changes can't be prevented.
2005
# Having calculated this though, we can't just call into
2006
# set_last_revision_info as a simple call, because there is a set_rh
2007
# hook that some folk may still be using.
2008
history = self._lefthand_history(revision_id)
2009
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2010
self._clear_cached_state()
2011
response = self._call('Branch.set_last_revision',
2012
self._remote_path(), self._lock_token, self._repo_lock_token,
2014
if response != ('ok',):
2015
raise errors.UnexpectedSmartServerResponse(response)
2016
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2019
def set_revision_history(self, rev_history):
2020
# Send just the tip revision of the history; the server will generate
2021
# the full history from that. If the revision doesn't exist in this
2022
# branch, NoSuchRevision will be raised.
2023
if rev_history == []:
2026
rev_id = rev_history[-1]
2027
self._set_last_revision(rev_id)
2028
for hook in branch.Branch.hooks['set_rh']:
2029
hook(self, rev_history)
2030
self._cache_revision_history(rev_history)
2032
def get_parent(self):
2034
return self._real_branch.get_parent()
2036
def _get_parent_location(self):
2037
# Used by tests, when checking normalisation of given vs stored paths.
2039
return self._real_branch._get_parent_location()
2041
def set_parent(self, url):
2043
return self._real_branch.set_parent(url)
2045
def _set_parent_location(self, url):
2046
# Used by tests, to poke bad urls into branch configurations
2048
self.set_parent(url)
2051
return self._real_branch._set_parent_location(url)
2053
def set_stacked_on_url(self, stacked_location):
2054
"""Set the URL this branch is stacked against.
2056
:raises UnstackableBranchFormat: If the branch does not support
2058
:raises UnstackableRepositoryFormat: If the repository does not support
2062
return self._real_branch.set_stacked_on_url(stacked_location)
2065
def pull(self, source, overwrite=False, stop_revision=None,
2067
self._clear_cached_state_of_remote_branch_only()
2069
return self._real_branch.pull(
2070
source, overwrite=overwrite, stop_revision=stop_revision,
2071
_override_hook_target=self, **kwargs)
2074
def push(self, target, overwrite=False, stop_revision=None):
2076
return self._real_branch.push(
2077
target, overwrite=overwrite, stop_revision=stop_revision,
2078
_override_hook_source_branch=self)
2080
def is_locked(self):
2081
return self._lock_count >= 1
2084
def revision_id_to_revno(self, revision_id):
2086
return self._real_branch.revision_id_to_revno(revision_id)
2089
def set_last_revision_info(self, revno, revision_id):
2090
# XXX: These should be returned by the set_last_revision_info verb
2091
old_revno, old_revid = self.last_revision_info()
2092
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2093
revision_id = ensure_null(revision_id)
2095
response = self._call('Branch.set_last_revision_info',
2096
self._remote_path(), self._lock_token, self._repo_lock_token,
2097
str(revno), revision_id)
2098
except errors.UnknownSmartMethod:
2100
self._clear_cached_state_of_remote_branch_only()
2101
self._real_branch.set_last_revision_info(revno, revision_id)
2102
self._last_revision_info_cache = revno, revision_id
2104
if response == ('ok',):
2105
self._clear_cached_state()
2106
self._last_revision_info_cache = revno, revision_id
2107
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2108
# Update the _real_branch's cache too.
2109
if self._real_branch is not None:
2110
cache = self._last_revision_info_cache
2111
self._real_branch._last_revision_info_cache = cache
2113
raise errors.UnexpectedSmartServerResponse(response)
2116
def generate_revision_history(self, revision_id, last_rev=None,
2118
medium = self._client._medium
2119
if not medium._is_remote_before((1, 6)):
2120
# Use a smart method for 1.6 and above servers
2122
self._set_last_revision_descendant(revision_id, other_branch,
2123
allow_diverged=True, allow_overwrite_descendant=True)
2125
except errors.UnknownSmartMethod:
2126
medium._remember_remote_is_before((1, 6))
2127
self._clear_cached_state_of_remote_branch_only()
2128
self.set_revision_history(self._lefthand_history(revision_id,
2129
last_rev=last_rev,other_branch=other_branch))
2134
return self._real_branch.tags
2136
def set_push_location(self, location):
2138
return self._real_branch.set_push_location(location)
2141
def _extract_tar(tar, to_dir):
2142
"""Extract all the contents of a tarfile object.
2144
A replacement for extractall, which is not present in python2.4
2147
tar.extract(tarinfo, to_dir)
2150
def _translate_error(err, **context):
2151
"""Translate an ErrorFromSmartServer into a more useful error.
2153
Possible context keys:
2161
If the error from the server doesn't match a known pattern, then
2162
UnknownErrorFromSmartServer is raised.
2166
return context[name]
2167
except KeyError, key_err:
2168
mutter('Missing key %r in context %r', key_err.args[0], context)
2171
"""Get the path from the context if present, otherwise use first error
2175
return context['path']
2176
except KeyError, key_err:
2178
return err.error_args[0]
2179
except IndexError, idx_err:
2181
'Missing key %r in context %r', key_err.args[0], context)
2184
if err.error_verb == 'NoSuchRevision':
2185
raise NoSuchRevision(find('branch'), err.error_args[0])
2186
elif err.error_verb == 'nosuchrevision':
2187
raise NoSuchRevision(find('repository'), err.error_args[0])
2188
elif err.error_tuple == ('nobranch',):
2189
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2190
elif err.error_verb == 'norepository':
2191
raise errors.NoRepositoryPresent(find('bzrdir'))
2192
elif err.error_verb == 'LockContention':
2193
raise errors.LockContention('(remote lock)')
2194
elif err.error_verb == 'UnlockableTransport':
2195
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2196
elif err.error_verb == 'LockFailed':
2197
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2198
elif err.error_verb == 'TokenMismatch':
2199
raise errors.TokenMismatch(find('token'), '(remote token)')
2200
elif err.error_verb == 'Diverged':
2201
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2202
elif err.error_verb == 'TipChangeRejected':
2203
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2204
elif err.error_verb == 'UnstackableBranchFormat':
2205
raise errors.UnstackableBranchFormat(*err.error_args)
2206
elif err.error_verb == 'UnstackableRepositoryFormat':
2207
raise errors.UnstackableRepositoryFormat(*err.error_args)
2208
elif err.error_verb == 'NotStacked':
2209
raise errors.NotStacked(branch=find('branch'))
2210
elif err.error_verb == 'PermissionDenied':
2212
if len(err.error_args) >= 2:
2213
extra = err.error_args[1]
2216
raise errors.PermissionDenied(path, extra=extra)
2217
elif err.error_verb == 'ReadError':
2219
raise errors.ReadError(path)
2220
elif err.error_verb == 'NoSuchFile':
2222
raise errors.NoSuchFile(path)
2223
elif err.error_verb == 'FileExists':
2224
raise errors.FileExists(err.error_args[0])
2225
elif err.error_verb == 'DirectoryNotEmpty':
2226
raise errors.DirectoryNotEmpty(err.error_args[0])
2227
elif err.error_verb == 'ShortReadvError':
2228
args = err.error_args
2229
raise errors.ShortReadvError(
2230
args[0], int(args[1]), int(args[2]), int(args[3]))
2231
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2232
encoding = str(err.error_args[0]) # encoding must always be a string
2233
val = err.error_args[1]
2234
start = int(err.error_args[2])
2235
end = int(err.error_args[3])
2236
reason = str(err.error_args[4]) # reason must always be a string
2237
if val.startswith('u:'):
2238
val = val[2:].decode('utf-8')
2239
elif val.startswith('s:'):
2240
val = val[2:].decode('base64')
2241
if err.error_verb == 'UnicodeDecodeError':
2242
raise UnicodeDecodeError(encoding, val, start, end, reason)
2243
elif err.error_verb == 'UnicodeEncodeError':
2244
raise UnicodeEncodeError(encoding, val, start, end, reason)
2245
elif err.error_verb == 'ReadOnlyError':
2246
raise errors.TransportNotPossible('readonly transport')
2247
raise errors.UnknownErrorFromSmartServer(err)