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_repo_v1(self, path):
223
verb = 'BzrDir.find_repository'
224
response = self._call(verb, path)
225
if response[0] != 'ok':
226
raise errors.UnexpectedSmartServerResponse(response)
227
# servers that only support the v1 method don't support external
230
repo = self._real_bzrdir.open_repository()
231
response = response + ('no', repo._format.network_name())
232
return response, repo
234
def _open_repo_v2(self, path):
235
verb = 'BzrDir.find_repositoryV2'
236
response = self._call(verb, path)
237
if response[0] != 'ok':
238
raise errors.UnexpectedSmartServerResponse(response)
240
repo = self._real_bzrdir.open_repository()
241
response = response + (repo._format.network_name(),)
242
return response, repo
244
def _open_repo_v3(self, path):
245
verb = 'BzrDir.find_repositoryV3'
246
medium = self._client._medium
247
if medium._is_remote_before((1, 13)):
248
raise errors.UnknownSmartMethod(verb)
249
response = self._call(verb, path)
250
if response[0] != 'ok':
251
raise errors.UnexpectedSmartServerResponse(response)
252
return response, None
254
def open_repository(self):
255
path = self._path_for_remote_call(self._client)
257
for probe in [self._open_repo_v3, self._open_repo_v2,
260
response, real_repo = probe(path)
262
except errors.UnknownSmartMethod:
265
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
266
if response[0] != 'ok':
267
raise errors.UnexpectedSmartServerResponse(response)
268
if len(response) != 6:
269
raise SmartProtocolError('incorrect response length %s' % (response,))
270
if response[1] == '':
271
# repo is at this dir.
272
format = response_tuple_to_repo_format(response[2:])
273
# Used to support creating a real format instance when needed.
274
format._creating_bzrdir = self
275
remote_repo = RemoteRepository(self, format)
276
format._creating_repo = remote_repo
277
if real_repo is not None:
278
remote_repo._set_real_repository(real_repo)
281
raise errors.NoRepositoryPresent(self)
283
def open_workingtree(self, recommend_upgrade=True):
285
if self._real_bzrdir.has_workingtree():
286
raise errors.NotLocalUrl(self.root_transport)
288
raise errors.NoWorkingTree(self.root_transport.base)
290
def _path_for_remote_call(self, client):
291
"""Return the path to be used for this bzrdir in a remote call."""
292
return client.remote_path_from_transport(self.root_transport)
294
def get_branch_transport(self, branch_format):
296
return self._real_bzrdir.get_branch_transport(branch_format)
298
def get_repository_transport(self, repository_format):
300
return self._real_bzrdir.get_repository_transport(repository_format)
302
def get_workingtree_transport(self, workingtree_format):
304
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
306
def can_convert_format(self):
307
"""Upgrading of remote bzrdirs is not supported yet."""
310
def needs_format_conversion(self, format=None):
311
"""Upgrading of remote bzrdirs is not supported yet."""
313
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
314
% 'needs_format_conversion(format=None)')
317
def clone(self, url, revision_id=None, force_new_repo=False,
318
preserve_stacking=False):
320
return self._real_bzrdir.clone(url, revision_id=revision_id,
321
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
323
def get_config(self):
325
return self._real_bzrdir.get_config()
328
class RemoteRepositoryFormat(repository.RepositoryFormat):
329
"""Format for repositories accessed over a _SmartClient.
331
Instances of this repository are represented by RemoteRepository
334
The RemoteRepositoryFormat is parameterized during construction
335
to reflect the capabilities of the real, remote format. Specifically
336
the attributes rich_root_data and supports_tree_reference are set
337
on a per instance basis, and are not set (and should not be) at
340
:ivar _custom_format: If set, a specific concrete repository format that
341
will be used when initializing a repository with this
342
RemoteRepositoryFormat.
343
:ivar _creating_repo: If set, the repository object that this
344
RemoteRepositoryFormat was created for: it can be called into
345
to obtain data like the network name.
348
_matchingbzrdir = RemoteBzrDirFormat()
351
repository.RepositoryFormat.__init__(self)
352
self._custom_format = None
353
self._network_name = None
354
self._creating_bzrdir = None
356
def _vfs_initialize(self, a_bzrdir, shared):
357
"""Helper for common code in initialize."""
358
if self._custom_format:
359
# Custom format requested
360
result = self._custom_format.initialize(a_bzrdir, shared=shared)
361
elif self._creating_bzrdir is not None:
362
# Use the format that the repository we were created to back
364
prior_repo = self._creating_bzrdir.open_repository()
365
prior_repo._ensure_real()
366
result = prior_repo._real_repository._format.initialize(
367
a_bzrdir, shared=shared)
369
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
370
# support remote initialization.
371
# We delegate to a real object at this point (as RemoteBzrDir
372
# delegate to the repository format which would lead to infinite
373
# recursion if we just called a_bzrdir.create_repository.
374
a_bzrdir._ensure_real()
375
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
376
if not isinstance(result, RemoteRepository):
377
return self.open(a_bzrdir)
381
def initialize(self, a_bzrdir, shared=False):
382
# Being asked to create on a non RemoteBzrDir:
383
if not isinstance(a_bzrdir, RemoteBzrDir):
384
return self._vfs_initialize(a_bzrdir, shared)
385
medium = a_bzrdir._client._medium
386
if medium._is_remote_before((1, 13)):
387
return self._vfs_initialize(a_bzrdir, shared)
388
# Creating on a remote bzr dir.
389
# 1) get the network name to use.
390
if self._custom_format:
391
network_name = self._custom_format.network_name()
393
# Select the current bzrlib default and ask for that.
394
reference_bzrdir_format = bzrdir.format_registry.get('default')()
395
reference_format = reference_bzrdir_format.repository_format
396
network_name = reference_format.network_name()
397
# 2) try direct creation via RPC
398
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
399
verb = 'BzrDir.create_repository'
405
response = a_bzrdir._call(verb, path, network_name, shared_str)
406
except errors.UnknownSmartMethod:
407
# Fallback - use vfs methods
408
return self._vfs_initialize(a_bzrdir, shared)
410
# Turn the response into a RemoteRepository object.
411
format = response_tuple_to_repo_format(response[1:])
412
# Used to support creating a real format instance when needed.
413
format._creating_bzrdir = a_bzrdir
414
remote_repo = RemoteRepository(a_bzrdir, format)
415
format._creating_repo = remote_repo
418
def open(self, a_bzrdir):
419
if not isinstance(a_bzrdir, RemoteBzrDir):
420
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
421
return a_bzrdir.open_repository()
423
def _ensure_real(self):
424
if self._custom_format is None:
425
self._custom_format = repository.network_format_registry.get(
429
def _fetch_order(self):
431
return self._custom_format._fetch_order
434
def _fetch_uses_deltas(self):
436
return self._custom_format._fetch_uses_deltas
439
def _fetch_reconcile(self):
441
return self._custom_format._fetch_reconcile
443
def get_format_description(self):
444
return 'bzr remote repository'
446
def __eq__(self, other):
447
return self.__class__ == other.__class__
449
def check_conversion_target(self, target_format):
450
if self.rich_root_data and not target_format.rich_root_data:
451
raise errors.BadConversionTarget(
452
'Does not support rich root data.', target_format)
453
if (self.supports_tree_reference and
454
not getattr(target_format, 'supports_tree_reference', False)):
455
raise errors.BadConversionTarget(
456
'Does not support nested trees', target_format)
458
def network_name(self):
459
if self._network_name:
460
return self._network_name
461
self._creating_repo._ensure_real()
462
return self._creating_repo._real_repository._format.network_name()
465
def _serializer(self):
467
return self._custom_format._serializer
470
class RemoteRepository(_RpcHelper):
471
"""Repository accessed over rpc.
473
For the moment most operations are performed using local transport-backed
477
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
478
"""Create a RemoteRepository instance.
480
:param remote_bzrdir: The bzrdir hosting this repository.
481
:param format: The RemoteFormat object to use.
482
:param real_repository: If not None, a local implementation of the
483
repository logic for the repository, usually accessing the data
485
:param _client: Private testing parameter - override the smart client
486
to be used by the repository.
489
self._real_repository = real_repository
491
self._real_repository = None
492
self.bzrdir = remote_bzrdir
494
self._client = remote_bzrdir._client
496
self._client = _client
497
self._format = format
498
self._lock_mode = None
499
self._lock_token = None
501
self._leave_lock = False
502
self._unstacked_provider = graph.CachingParentsProvider(
503
get_parent_map=self._get_parent_map_rpc)
504
self._unstacked_provider.disable_cache()
506
# These depend on the actual remote format, so force them off for
507
# maximum compatibility. XXX: In future these should depend on the
508
# remote repository instance, but this is irrelevant until we perform
509
# reconcile via an RPC call.
510
self._reconcile_does_inventory_gc = False
511
self._reconcile_fixes_text_parents = False
512
self._reconcile_backsup_inventory = False
513
self.base = self.bzrdir.transport.base
514
# Additional places to query for data.
515
self._fallback_repositories = []
518
return "%s(%s)" % (self.__class__.__name__, self.base)
522
def abort_write_group(self, suppress_errors=False):
523
"""Complete a write group on the decorated repository.
525
Smart methods peform operations in a single step so this api
526
is not really applicable except as a compatibility thunk
527
for older plugins that don't use e.g. the CommitBuilder
530
:param suppress_errors: see Repository.abort_write_group.
533
return self._real_repository.abort_write_group(
534
suppress_errors=suppress_errors)
536
def commit_write_group(self):
537
"""Complete a write group on the decorated repository.
539
Smart methods peform operations in a single step so this api
540
is not really applicable except as a compatibility thunk
541
for older plugins that don't use e.g. the CommitBuilder
545
return self._real_repository.commit_write_group()
547
def resume_write_group(self, tokens):
549
return self._real_repository.resume_write_group(tokens)
551
def suspend_write_group(self):
553
return self._real_repository.suspend_write_group()
555
def _ensure_real(self):
556
"""Ensure that there is a _real_repository set.
558
Used before calls to self._real_repository.
560
if self._real_repository is None:
561
self.bzrdir._ensure_real()
562
self._set_real_repository(
563
self.bzrdir._real_bzrdir.open_repository())
565
def _translate_error(self, err, **context):
566
self.bzrdir._translate_error(err, repository=self, **context)
568
def find_text_key_references(self):
569
"""Find the text key references within the repository.
571
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
572
revision_ids. Each altered file-ids has the exact revision_ids that
573
altered it listed explicitly.
574
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
575
to whether they were referred to by the inventory of the
576
revision_id that they contain. The inventory texts from all present
577
revision ids are assessed to generate this report.
580
return self._real_repository.find_text_key_references()
582
def _generate_text_key_index(self):
583
"""Generate a new text key index for the repository.
585
This is an expensive function that will take considerable time to run.
587
:return: A dict mapping (file_id, revision_id) tuples to a list of
588
parents, also (file_id, revision_id) tuples.
591
return self._real_repository._generate_text_key_index()
593
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
594
def get_revision_graph(self, revision_id=None):
595
"""See Repository.get_revision_graph()."""
596
return self._get_revision_graph(revision_id)
598
def _get_revision_graph(self, revision_id):
599
"""Private method for using with old (< 1.2) servers to fallback."""
600
if revision_id is None:
602
elif revision.is_null(revision_id):
605
path = self.bzrdir._path_for_remote_call(self._client)
606
response = self._call_expecting_body(
607
'Repository.get_revision_graph', path, revision_id)
608
response_tuple, response_handler = response
609
if response_tuple[0] != 'ok':
610
raise errors.UnexpectedSmartServerResponse(response_tuple)
611
coded = response_handler.read_body_bytes()
613
# no revisions in this repository!
615
lines = coded.split('\n')
618
d = tuple(line.split())
619
revision_graph[d[0]] = d[1:]
621
return revision_graph
624
"""See Repository._get_sink()."""
625
return RemoteStreamSink(self)
627
def has_revision(self, revision_id):
628
"""See Repository.has_revision()."""
629
if revision_id == NULL_REVISION:
630
# The null revision is always present.
632
path = self.bzrdir._path_for_remote_call(self._client)
633
response = self._call('Repository.has_revision', path, revision_id)
634
if response[0] not in ('yes', 'no'):
635
raise errors.UnexpectedSmartServerResponse(response)
636
if response[0] == 'yes':
638
for fallback_repo in self._fallback_repositories:
639
if fallback_repo.has_revision(revision_id):
643
def has_revisions(self, revision_ids):
644
"""See Repository.has_revisions()."""
645
# FIXME: This does many roundtrips, particularly when there are
646
# fallback repositories. -- mbp 20080905
648
for revision_id in revision_ids:
649
if self.has_revision(revision_id):
650
result.add(revision_id)
653
def has_same_location(self, other):
654
return (self.__class__ == other.__class__ and
655
self.bzrdir.transport.base == other.bzrdir.transport.base)
657
def get_graph(self, other_repository=None):
658
"""Return the graph for this repository format"""
659
parents_provider = self._make_parents_provider(other_repository)
660
return graph.Graph(parents_provider)
662
def gather_stats(self, revid=None, committers=None):
663
"""See Repository.gather_stats()."""
664
path = self.bzrdir._path_for_remote_call(self._client)
665
# revid can be None to indicate no revisions, not just NULL_REVISION
666
if revid is None or revision.is_null(revid):
670
if committers is None or not committers:
671
fmt_committers = 'no'
673
fmt_committers = 'yes'
674
response_tuple, response_handler = self._call_expecting_body(
675
'Repository.gather_stats', path, fmt_revid, fmt_committers)
676
if response_tuple[0] != 'ok':
677
raise errors.UnexpectedSmartServerResponse(response_tuple)
679
body = response_handler.read_body_bytes()
681
for line in body.split('\n'):
684
key, val_text = line.split(':')
685
if key in ('revisions', 'size', 'committers'):
686
result[key] = int(val_text)
687
elif key in ('firstrev', 'latestrev'):
688
values = val_text.split(' ')[1:]
689
result[key] = (float(values[0]), long(values[1]))
693
def find_branches(self, using=False):
694
"""See Repository.find_branches()."""
695
# should be an API call to the server.
697
return self._real_repository.find_branches(using=using)
699
def get_physical_lock_status(self):
700
"""See Repository.get_physical_lock_status()."""
701
# should be an API call to the server.
703
return self._real_repository.get_physical_lock_status()
705
def is_in_write_group(self):
706
"""Return True if there is an open write group.
708
write groups are only applicable locally for the smart server..
710
if self._real_repository:
711
return self._real_repository.is_in_write_group()
714
return self._lock_count >= 1
717
"""See Repository.is_shared()."""
718
path = self.bzrdir._path_for_remote_call(self._client)
719
response = self._call('Repository.is_shared', path)
720
if response[0] not in ('yes', 'no'):
721
raise SmartProtocolError('unexpected response code %s' % (response,))
722
return response[0] == 'yes'
724
def is_write_locked(self):
725
return self._lock_mode == 'w'
728
# wrong eventually - want a local lock cache context
729
if not self._lock_mode:
730
self._lock_mode = 'r'
732
self._unstacked_provider.enable_cache(cache_misses=False)
733
if self._real_repository is not None:
734
self._real_repository.lock_read()
736
self._lock_count += 1
738
def _remote_lock_write(self, token):
739
path = self.bzrdir._path_for_remote_call(self._client)
742
err_context = {'token': token}
743
response = self._call('Repository.lock_write', path, token,
745
if response[0] == 'ok':
749
raise errors.UnexpectedSmartServerResponse(response)
751
def lock_write(self, token=None, _skip_rpc=False):
752
if not self._lock_mode:
754
if self._lock_token is not None:
755
if token != self._lock_token:
756
raise errors.TokenMismatch(token, self._lock_token)
757
self._lock_token = token
759
self._lock_token = self._remote_lock_write(token)
760
# if self._lock_token is None, then this is something like packs or
761
# svn where we don't get to lock the repo, or a weave style repository
762
# where we cannot lock it over the wire and attempts to do so will
764
if self._real_repository is not None:
765
self._real_repository.lock_write(token=self._lock_token)
766
if token is not None:
767
self._leave_lock = True
769
self._leave_lock = False
770
self._lock_mode = 'w'
772
self._unstacked_provider.enable_cache(cache_misses=False)
773
elif self._lock_mode == 'r':
774
raise errors.ReadOnlyError(self)
776
self._lock_count += 1
777
return self._lock_token or None
779
def leave_lock_in_place(self):
780
if not self._lock_token:
781
raise NotImplementedError(self.leave_lock_in_place)
782
self._leave_lock = True
784
def dont_leave_lock_in_place(self):
785
if not self._lock_token:
786
raise NotImplementedError(self.dont_leave_lock_in_place)
787
self._leave_lock = False
789
def _set_real_repository(self, repository):
790
"""Set the _real_repository for this repository.
792
:param repository: The repository to fallback to for non-hpss
793
implemented operations.
795
if self._real_repository is not None:
796
# Replacing an already set real repository.
797
# We cannot do this [currently] if the repository is locked -
798
# synchronised state might be lost.
800
raise AssertionError('_real_repository is already set')
801
if isinstance(repository, RemoteRepository):
802
raise AssertionError()
803
self._real_repository = repository
804
for fb in self._fallback_repositories:
805
self._real_repository.add_fallback_repository(fb)
806
if self._lock_mode == 'w':
807
# if we are already locked, the real repository must be able to
808
# acquire the lock with our token.
809
self._real_repository.lock_write(self._lock_token)
810
elif self._lock_mode == 'r':
811
self._real_repository.lock_read()
813
def start_write_group(self):
814
"""Start a write group on the decorated repository.
816
Smart methods peform operations in a single step so this api
817
is not really applicable except as a compatibility thunk
818
for older plugins that don't use e.g. the CommitBuilder
822
return self._real_repository.start_write_group()
824
def _unlock(self, token):
825
path = self.bzrdir._path_for_remote_call(self._client)
827
# with no token the remote repository is not persistently locked.
829
err_context = {'token': token}
830
response = self._call('Repository.unlock', path, token,
832
if response == ('ok',):
835
raise errors.UnexpectedSmartServerResponse(response)
838
if not self._lock_count:
839
raise errors.LockNotHeld(self)
840
self._lock_count -= 1
841
if self._lock_count > 0:
843
self._unstacked_provider.disable_cache()
844
old_mode = self._lock_mode
845
self._lock_mode = None
847
# The real repository is responsible at present for raising an
848
# exception if it's in an unfinished write group. However, it
849
# normally will *not* actually remove the lock from disk - that's
850
# done by the server on receiving the Repository.unlock call.
851
# This is just to let the _real_repository stay up to date.
852
if self._real_repository is not None:
853
self._real_repository.unlock()
855
# The rpc-level lock should be released even if there was a
856
# problem releasing the vfs-based lock.
858
# Only write-locked repositories need to make a remote method
859
# call to perfom the unlock.
860
old_token = self._lock_token
861
self._lock_token = None
862
if not self._leave_lock:
863
self._unlock(old_token)
865
def break_lock(self):
866
# should hand off to the network
868
return self._real_repository.break_lock()
870
def _get_tarball(self, compression):
871
"""Return a TemporaryFile containing a repository tarball.
873
Returns None if the server does not support sending tarballs.
876
path = self.bzrdir._path_for_remote_call(self._client)
878
response, protocol = self._call_expecting_body(
879
'Repository.tarball', path, compression)
880
except errors.UnknownSmartMethod:
881
protocol.cancel_read_body()
883
if response[0] == 'ok':
884
# Extract the tarball and return it
885
t = tempfile.NamedTemporaryFile()
886
# TODO: rpc layer should read directly into it...
887
t.write(protocol.read_body_bytes())
890
raise errors.UnexpectedSmartServerResponse(response)
892
def sprout(self, to_bzrdir, revision_id=None):
893
# TODO: Option to control what format is created?
895
dest_repo = self._real_repository._format.initialize(to_bzrdir,
897
dest_repo.fetch(self, revision_id=revision_id)
900
### These methods are just thin shims to the VFS object for now.
902
def revision_tree(self, revision_id):
904
return self._real_repository.revision_tree(revision_id)
906
def get_serializer_format(self):
908
return self._real_repository.get_serializer_format()
910
def get_commit_builder(self, branch, parents, config, timestamp=None,
911
timezone=None, committer=None, revprops=None,
913
# FIXME: It ought to be possible to call this without immediately
914
# triggering _ensure_real. For now it's the easiest thing to do.
916
real_repo = self._real_repository
917
builder = real_repo.get_commit_builder(branch, parents,
918
config, timestamp=timestamp, timezone=timezone,
919
committer=committer, revprops=revprops, revision_id=revision_id)
922
def add_fallback_repository(self, repository):
923
"""Add a repository to use for looking up data not held locally.
925
:param repository: A repository.
927
# XXX: At the moment the RemoteRepository will allow fallbacks
928
# unconditionally - however, a _real_repository will usually exist,
929
# and may raise an error if it's not accommodated by the underlying
930
# format. Eventually we should check when opening the repository
931
# whether it's willing to allow them or not.
933
# We need to accumulate additional repositories here, to pass them in
936
self._fallback_repositories.append(repository)
937
# If self._real_repository was parameterised already (e.g. because a
938
# _real_branch had its get_stacked_on_url method called), then the
939
# repository to be added may already be in the _real_repositories list.
940
if self._real_repository is not None:
941
if repository not in self._real_repository._fallback_repositories:
942
self._real_repository.add_fallback_repository(repository)
944
# They are also seen by the fallback repository. If it doesn't
945
# exist yet they'll be added then. This implicitly copies them.
948
def add_inventory(self, revid, inv, parents):
950
return self._real_repository.add_inventory(revid, inv, parents)
952
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
955
return self._real_repository.add_inventory_by_delta(basis_revision_id,
956
delta, new_revision_id, parents)
958
def add_revision(self, rev_id, rev, inv=None, config=None):
960
return self._real_repository.add_revision(
961
rev_id, rev, inv=inv, config=config)
964
def get_inventory(self, revision_id):
966
return self._real_repository.get_inventory(revision_id)
968
def iter_inventories(self, revision_ids):
970
return self._real_repository.iter_inventories(revision_ids)
973
def get_revision(self, revision_id):
975
return self._real_repository.get_revision(revision_id)
977
def get_transaction(self):
979
return self._real_repository.get_transaction()
982
def clone(self, a_bzrdir, revision_id=None):
984
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
986
def make_working_trees(self):
987
"""See Repository.make_working_trees"""
989
return self._real_repository.make_working_trees()
991
def revision_ids_to_search_result(self, result_set):
992
"""Convert a set of revision ids to a graph SearchResult."""
993
result_parents = set()
994
for parents in self.get_graph().get_parent_map(
995
result_set).itervalues():
996
result_parents.update(parents)
997
included_keys = result_set.intersection(result_parents)
998
start_keys = result_set.difference(included_keys)
999
exclude_keys = result_parents.difference(result_set)
1000
result = graph.SearchResult(start_keys, exclude_keys,
1001
len(result_set), result_set)
1005
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1006
"""Return the revision ids that other has that this does not.
1008
These are returned in topological order.
1010
revision_id: only return revision ids included by revision_id.
1012
return repository.InterRepository.get(
1013
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1015
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
1016
# Not delegated to _real_repository so that InterRepository.get has a
1017
# chance to find an InterRepository specialised for RemoteRepository.
1018
if self.has_same_location(source):
1019
# check that last_revision is in 'from' and then return a
1021
if (revision_id is not None and
1022
not revision.is_null(revision_id)):
1023
self.get_revision(revision_id)
1025
inter = repository.InterRepository.get(source, self)
1027
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
1028
except NotImplementedError:
1029
raise errors.IncompatibleRepositories(source, self)
1031
def create_bundle(self, target, base, fileobj, format=None):
1033
self._real_repository.create_bundle(target, base, fileobj, format)
1036
def get_ancestry(self, revision_id, topo_sorted=True):
1038
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1040
def fileids_altered_by_revision_ids(self, revision_ids):
1042
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1044
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1046
return self._real_repository._get_versioned_file_checker(
1047
revisions, revision_versions_cache)
1049
def iter_files_bytes(self, desired_files):
1050
"""See Repository.iter_file_bytes.
1053
return self._real_repository.iter_files_bytes(desired_files)
1055
def get_parent_map(self, revision_ids):
1056
"""See bzrlib.Graph.get_parent_map()."""
1057
return self._make_parents_provider().get_parent_map(revision_ids)
1059
def _get_parent_map_rpc(self, keys):
1060
"""Helper for get_parent_map that performs the RPC."""
1061
medium = self._client._medium
1062
if medium._is_remote_before((1, 2)):
1063
# We already found out that the server can't understand
1064
# Repository.get_parent_map requests, so just fetch the whole
1066
# XXX: Note that this will issue a deprecation warning. This is ok
1067
# :- its because we're working with a deprecated server anyway, and
1068
# the user will almost certainly have seen a warning about the
1069
# server version already.
1070
rg = self.get_revision_graph()
1071
# There is an api discrepency between get_parent_map and
1072
# get_revision_graph. Specifically, a "key:()" pair in
1073
# get_revision_graph just means a node has no parents. For
1074
# "get_parent_map" it means the node is a ghost. So fix up the
1075
# graph to correct this.
1076
# https://bugs.launchpad.net/bzr/+bug/214894
1077
# There is one other "bug" which is that ghosts in
1078
# get_revision_graph() are not returned at all. But we won't worry
1079
# about that for now.
1080
for node_id, parent_ids in rg.iteritems():
1081
if parent_ids == ():
1082
rg[node_id] = (NULL_REVISION,)
1083
rg[NULL_REVISION] = ()
1088
raise ValueError('get_parent_map(None) is not valid')
1089
if NULL_REVISION in keys:
1090
keys.discard(NULL_REVISION)
1091
found_parents = {NULL_REVISION:()}
1093
return found_parents
1096
# TODO(Needs analysis): We could assume that the keys being requested
1097
# from get_parent_map are in a breadth first search, so typically they
1098
# will all be depth N from some common parent, and we don't have to
1099
# have the server iterate from the root parent, but rather from the
1100
# keys we're searching; and just tell the server the keyspace we
1101
# already have; but this may be more traffic again.
1103
# Transform self._parents_map into a search request recipe.
1104
# TODO: Manage this incrementally to avoid covering the same path
1105
# repeatedly. (The server will have to on each request, but the less
1106
# work done the better).
1107
parents_map = self._unstacked_provider.get_cached_map()
1108
if parents_map is None:
1109
# Repository is not locked, so there's no cache.
1111
start_set = set(parents_map)
1112
result_parents = set()
1113
for parents in parents_map.itervalues():
1114
result_parents.update(parents)
1115
stop_keys = result_parents.difference(start_set)
1116
included_keys = start_set.intersection(result_parents)
1117
start_set.difference_update(included_keys)
1118
recipe = (start_set, stop_keys, len(parents_map))
1119
body = self._serialise_search_recipe(recipe)
1120
path = self.bzrdir._path_for_remote_call(self._client)
1122
if type(key) is not str:
1124
"key %r not a plain string" % (key,))
1125
verb = 'Repository.get_parent_map'
1126
args = (path,) + tuple(keys)
1128
response = self._call_with_body_bytes_expecting_body(
1130
except errors.UnknownSmartMethod:
1131
# Server does not support this method, so get the whole graph.
1132
# Worse, we have to force a disconnection, because the server now
1133
# doesn't realise it has a body on the wire to consume, so the
1134
# only way to recover is to abandon the connection.
1136
'Server is too old for fast get_parent_map, reconnecting. '
1137
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1139
# To avoid having to disconnect repeatedly, we keep track of the
1140
# fact the server doesn't understand remote methods added in 1.2.
1141
medium._remember_remote_is_before((1, 2))
1142
return self.get_revision_graph(None)
1143
response_tuple, response_handler = response
1144
if response_tuple[0] not in ['ok']:
1145
response_handler.cancel_read_body()
1146
raise errors.UnexpectedSmartServerResponse(response_tuple)
1147
if response_tuple[0] == 'ok':
1148
coded = bz2.decompress(response_handler.read_body_bytes())
1150
# no revisions found
1152
lines = coded.split('\n')
1155
d = tuple(line.split())
1157
revision_graph[d[0]] = d[1:]
1159
# No parents - so give the Graph result (NULL_REVISION,).
1160
revision_graph[d[0]] = (NULL_REVISION,)
1161
return revision_graph
1164
def get_signature_text(self, revision_id):
1166
return self._real_repository.get_signature_text(revision_id)
1169
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1170
def get_revision_graph_with_ghosts(self, revision_ids=None):
1172
return self._real_repository.get_revision_graph_with_ghosts(
1173
revision_ids=revision_ids)
1176
def get_inventory_xml(self, revision_id):
1178
return self._real_repository.get_inventory_xml(revision_id)
1180
def deserialise_inventory(self, revision_id, xml):
1182
return self._real_repository.deserialise_inventory(revision_id, xml)
1184
def reconcile(self, other=None, thorough=False):
1186
return self._real_repository.reconcile(other=other, thorough=thorough)
1188
def all_revision_ids(self):
1190
return self._real_repository.all_revision_ids()
1193
def get_deltas_for_revisions(self, revisions):
1195
return self._real_repository.get_deltas_for_revisions(revisions)
1198
def get_revision_delta(self, revision_id):
1200
return self._real_repository.get_revision_delta(revision_id)
1203
def revision_trees(self, revision_ids):
1205
return self._real_repository.revision_trees(revision_ids)
1208
def get_revision_reconcile(self, revision_id):
1210
return self._real_repository.get_revision_reconcile(revision_id)
1213
def check(self, revision_ids=None):
1215
return self._real_repository.check(revision_ids=revision_ids)
1217
def copy_content_into(self, destination, revision_id=None):
1219
return self._real_repository.copy_content_into(
1220
destination, revision_id=revision_id)
1222
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1223
# get a tarball of the remote repository, and copy from that into the
1225
from bzrlib import osutils
1227
# TODO: Maybe a progress bar while streaming the tarball?
1228
note("Copying repository content as tarball...")
1229
tar_file = self._get_tarball('bz2')
1230
if tar_file is None:
1232
destination = to_bzrdir.create_repository()
1234
tar = tarfile.open('repository', fileobj=tar_file,
1236
tmpdir = osutils.mkdtemp()
1238
_extract_tar(tar, tmpdir)
1239
tmp_bzrdir = BzrDir.open(tmpdir)
1240
tmp_repo = tmp_bzrdir.open_repository()
1241
tmp_repo.copy_content_into(destination, revision_id)
1243
osutils.rmtree(tmpdir)
1247
# TODO: Suggestion from john: using external tar is much faster than
1248
# python's tarfile library, but it may not work on windows.
1251
def inventories(self):
1252
"""Decorate the real repository for now.
1254
In the long term a full blown network facility is needed to
1255
avoid creating a real repository object locally.
1258
return self._real_repository.inventories
1262
"""Compress the data within the repository.
1264
This is not currently implemented within the smart server.
1267
return self._real_repository.pack()
1270
def revisions(self):
1271
"""Decorate the real repository for now.
1273
In the short term this should become a real object to intercept graph
1276
In the long term a full blown network facility is needed.
1279
return self._real_repository.revisions
1281
def set_make_working_trees(self, new_value):
1283
new_value_str = "True"
1285
new_value_str = "False"
1286
path = self.bzrdir._path_for_remote_call(self._client)
1288
response = self._call(
1289
'Repository.set_make_working_trees', path, new_value_str)
1290
except errors.UnknownSmartMethod:
1292
self._real_repository.set_make_working_trees(new_value)
1294
if response[0] != 'ok':
1295
raise errors.UnexpectedSmartServerResponse(response)
1298
def signatures(self):
1299
"""Decorate the real repository for now.
1301
In the long term a full blown network facility is needed to avoid
1302
creating a real repository object locally.
1305
return self._real_repository.signatures
1308
def sign_revision(self, revision_id, gpg_strategy):
1310
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1314
"""Decorate the real repository for now.
1316
In the long term a full blown network facility is needed to avoid
1317
creating a real repository object locally.
1320
return self._real_repository.texts
1323
def get_revisions(self, revision_ids):
1325
return self._real_repository.get_revisions(revision_ids)
1327
def supports_rich_root(self):
1328
return self._format.rich_root_data
1330
def iter_reverse_revision_history(self, revision_id):
1332
return self._real_repository.iter_reverse_revision_history(revision_id)
1335
def _serializer(self):
1336
return self._format._serializer
1338
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1340
return self._real_repository.store_revision_signature(
1341
gpg_strategy, plaintext, revision_id)
1343
def add_signature_text(self, revision_id, signature):
1345
return self._real_repository.add_signature_text(revision_id, signature)
1347
def has_signature_for_revision_id(self, revision_id):
1349
return self._real_repository.has_signature_for_revision_id(revision_id)
1351
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1353
return self._real_repository.item_keys_introduced_by(revision_ids,
1354
_files_pb=_files_pb)
1356
def revision_graph_can_have_wrong_parents(self):
1357
# The answer depends on the remote repo format.
1359
return self._real_repository.revision_graph_can_have_wrong_parents()
1361
def _find_inconsistent_revision_parents(self):
1363
return self._real_repository._find_inconsistent_revision_parents()
1365
def _check_for_inconsistent_revision_parents(self):
1367
return self._real_repository._check_for_inconsistent_revision_parents()
1369
def _make_parents_provider(self, other=None):
1370
providers = [self._unstacked_provider]
1371
if other is not None:
1372
providers.insert(0, other)
1373
providers.extend(r._make_parents_provider() for r in
1374
self._fallback_repositories)
1375
return graph._StackedParentsProvider(providers)
1377
def _serialise_search_recipe(self, recipe):
1378
"""Serialise a graph search recipe.
1380
:param recipe: A search recipe (start, stop, count).
1381
:return: Serialised bytes.
1383
start_keys = ' '.join(recipe[0])
1384
stop_keys = ' '.join(recipe[1])
1385
count = str(recipe[2])
1386
return '\n'.join((start_keys, stop_keys, count))
1389
path = self.bzrdir._path_for_remote_call(self._client)
1391
response = self._call('PackRepository.autopack', path)
1392
except errors.UnknownSmartMethod:
1394
self._real_repository._pack_collection.autopack()
1396
if self._real_repository is not None:
1397
# Reset the real repository's cache of pack names.
1398
# XXX: At some point we may be able to skip this and just rely on
1399
# the automatic retry logic to do the right thing, but for now we
1400
# err on the side of being correct rather than being optimal.
1401
self._real_repository._pack_collection.reload_pack_names()
1402
if response[0] != 'ok':
1403
raise errors.UnexpectedSmartServerResponse(response)
1406
class RemoteStreamSink(repository.StreamSink):
1408
def __init__(self, target_repo):
1409
repository.StreamSink.__init__(self, target_repo)
1411
def _insert_real(self, stream, src_format, resume_tokens):
1412
self.target_repo._ensure_real()
1413
sink = self.target_repo._real_repository._get_sink()
1414
result = sink.insert_stream(stream, src_format, resume_tokens)
1416
self.target_repo.autopack()
1419
def insert_stream(self, stream, src_format, resume_tokens):
1420
repo = self.target_repo
1421
client = repo._client
1422
medium = client._medium
1423
if medium._is_remote_before((1, 13)):
1424
# No possible way this can work.
1425
return self._insert_real(stream, src_format, resume_tokens)
1426
path = repo.bzrdir._path_for_remote_call(client)
1427
if not resume_tokens:
1428
# XXX: Ugly but important for correctness, *will* be fixed during
1429
# 1.13 cycle. Pushing a stream that is interrupted results in a
1430
# fallback to the _real_repositories sink *with a partial stream*.
1431
# Thats bad because we insert less data than bzr expected. To avoid
1432
# this we do a trial push to make sure the verb is accessible, and
1433
# do not fallback when actually pushing the stream. A cleanup patch
1434
# is going to look at rewinding/restarting the stream/partial
1436
byte_stream = self._stream_to_byte_stream([], src_format)
1438
response = client.call_with_body_stream(
1439
('Repository.insert_stream', path, ''), byte_stream)
1440
except errors.UnknownSmartMethod:
1441
medium._remember_remote_is_before((1,13))
1442
return self._insert_real(stream, src_format, resume_tokens)
1443
byte_stream = self._stream_to_byte_stream(stream, src_format)
1444
resume_tokens = ' '.join(resume_tokens)
1445
response = client.call_with_body_stream(
1446
('Repository.insert_stream', path, resume_tokens), byte_stream)
1447
if response[0][0] not in ('ok', 'missing-basis'):
1448
raise errors.UnexpectedSmartServerResponse(response)
1449
if response[0][0] == 'missing-basis':
1450
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1451
resume_tokens = tokens
1452
return resume_tokens, missing_keys
1454
if self.target_repo._real_repository is not None:
1455
collection = getattr(self.target_repo._real_repository,
1456
'_pack_collection', None)
1457
if collection is not None:
1458
collection.reload_pack_names()
1461
def _stream_to_byte_stream(self, stream, src_format):
1463
pack_writer = pack.ContainerWriter(bytes.append)
1465
pack_writer.add_bytes_record(src_format.network_name(), '')
1467
def get_adapter(adapter_key):
1469
return adapters[adapter_key]
1471
adapter_factory = adapter_registry.get(adapter_key)
1472
adapter = adapter_factory(self)
1473
adapters[adapter_key] = adapter
1475
for substream_type, substream in stream:
1476
for record in substream:
1477
if record.storage_kind in ('chunked', 'fulltext'):
1478
serialised = record_to_fulltext_bytes(record)
1480
serialised = record.get_bytes_as(record.storage_kind)
1482
# Some streams embed the whole stream into the wire
1483
# representation of the first record, which means that
1484
# later records have no wire representation: we skip them.
1485
pack_writer.add_bytes_record(serialised, [(substream_type,)])
1494
class RemoteBranchLockableFiles(LockableFiles):
1495
"""A 'LockableFiles' implementation that talks to a smart server.
1497
This is not a public interface class.
1500
def __init__(self, bzrdir, _client):
1501
self.bzrdir = bzrdir
1502
self._client = _client
1503
self._need_find_modes = True
1504
LockableFiles.__init__(
1505
self, bzrdir.get_branch_transport(None),
1506
'lock', lockdir.LockDir)
1508
def _find_modes(self):
1509
# RemoteBranches don't let the client set the mode of control files.
1510
self._dir_mode = None
1511
self._file_mode = None
1514
class RemoteBranchFormat(branch.BranchFormat):
1517
super(RemoteBranchFormat, self).__init__()
1518
self._matchingbzrdir = RemoteBzrDirFormat()
1519
self._matchingbzrdir.set_branch_format(self)
1520
self._custom_format = None
1522
def __eq__(self, other):
1523
return (isinstance(other, RemoteBranchFormat) and
1524
self.__dict__ == other.__dict__)
1526
def get_format_description(self):
1527
return 'Remote BZR Branch'
1529
def network_name(self):
1530
return self._network_name
1532
def open(self, a_bzrdir):
1533
return a_bzrdir.open_branch()
1535
def _vfs_initialize(self, a_bzrdir):
1536
# Initialisation when using a local bzrdir object, or a non-vfs init
1537
# method is not available on the server.
1538
# self._custom_format is always set - the start of initialize ensures
1540
if isinstance(a_bzrdir, RemoteBzrDir):
1541
a_bzrdir._ensure_real()
1542
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1544
# We assume the bzrdir is parameterised; it may not be.
1545
result = self._custom_format.initialize(a_bzrdir)
1546
if (isinstance(a_bzrdir, RemoteBzrDir) and
1547
not isinstance(result, RemoteBranch)):
1548
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1551
def initialize(self, a_bzrdir):
1552
# 1) get the network name to use.
1553
if self._custom_format:
1554
network_name = self._custom_format.network_name()
1556
# Select the current bzrlib default and ask for that.
1557
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1558
reference_format = reference_bzrdir_format.get_branch_format()
1559
self._custom_format = reference_format
1560
network_name = reference_format.network_name()
1561
# Being asked to create on a non RemoteBzrDir:
1562
if not isinstance(a_bzrdir, RemoteBzrDir):
1563
return self._vfs_initialize(a_bzrdir)
1564
medium = a_bzrdir._client._medium
1565
if medium._is_remote_before((1, 13)):
1566
return self._vfs_initialize(a_bzrdir)
1567
# Creating on a remote bzr dir.
1568
# 2) try direct creation via RPC
1569
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1570
verb = 'BzrDir.create_branch'
1572
response = a_bzrdir._call(verb, path, network_name)
1573
except errors.UnknownSmartMethod:
1574
# Fallback - use vfs methods
1575
return self._vfs_initialize(a_bzrdir)
1576
if response[0] != 'ok':
1577
raise errors.UnexpectedSmartServerResponse(response)
1578
# Turn the response into a RemoteRepository object.
1579
format = RemoteBranchFormat()
1580
format._network_name = response[1]
1581
repo_format = response_tuple_to_repo_format(response[3:])
1582
if response[2] == '':
1583
repo_bzrdir = a_bzrdir
1585
repo_bzrdir = RemoteBzrDir(
1586
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1588
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1589
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1590
format=format, setup_stacking=False)
1591
# XXX: We know this is a new branch, so it must have revno 0, revid
1592
# NULL_REVISION. Creating the branch locked would make this be unable
1593
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1594
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1595
return remote_branch
1597
def supports_tags(self):
1598
# Remote branches might support tags, but we won't know until we
1599
# access the real remote branch.
1603
class RemoteBranch(branch.Branch, _RpcHelper):
1604
"""Branch stored on a server accessed by HPSS RPC.
1606
At the moment most operations are mapped down to simple file operations.
1609
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1610
_client=None, format=None, setup_stacking=True):
1611
"""Create a RemoteBranch instance.
1613
:param real_branch: An optional local implementation of the branch
1614
format, usually accessing the data via the VFS.
1615
:param _client: Private parameter for testing.
1616
:param format: A RemoteBranchFormat object, None to create one
1617
automatically. If supplied it should have a network_name already
1619
:param setup_stacking: If True make an RPC call to determine the
1620
stacked (or not) status of the branch. If False assume the branch
1623
# We intentionally don't call the parent class's __init__, because it
1624
# will try to assign to self.tags, which is a property in this subclass.
1625
# And the parent's __init__ doesn't do much anyway.
1626
self._revision_id_to_revno_cache = None
1627
self._partial_revision_id_to_revno_cache = {}
1628
self._revision_history_cache = None
1629
self._last_revision_info_cache = None
1630
self._merge_sorted_revisions_cache = None
1631
self.bzrdir = remote_bzrdir
1632
if _client is not None:
1633
self._client = _client
1635
self._client = remote_bzrdir._client
1636
self.repository = remote_repository
1637
if real_branch is not None:
1638
self._real_branch = real_branch
1639
# Give the remote repository the matching real repo.
1640
real_repo = self._real_branch.repository
1641
if isinstance(real_repo, RemoteRepository):
1642
real_repo._ensure_real()
1643
real_repo = real_repo._real_repository
1644
self.repository._set_real_repository(real_repo)
1645
# Give the branch the remote repository to let fast-pathing happen.
1646
self._real_branch.repository = self.repository
1648
self._real_branch = None
1649
# Fill out expected attributes of branch for bzrlib api users.
1650
self.base = self.bzrdir.root_transport.base
1651
self._control_files = None
1652
self._lock_mode = None
1653
self._lock_token = None
1654
self._repo_lock_token = None
1655
self._lock_count = 0
1656
self._leave_lock = False
1657
# Setup a format: note that we cannot call _ensure_real until all the
1658
# attributes above are set: This code cannot be moved higher up in this
1661
self._format = RemoteBranchFormat()
1662
if real_branch is not None:
1663
self._format._network_name = \
1664
self._real_branch._format.network_name()
1666
# # XXX: Need to get this from BzrDir.open_branch's return value.
1667
# self._ensure_real()
1668
# self._format._network_name = \
1669
# self._real_branch._format.network_name()
1671
self._format = format
1672
# The base class init is not called, so we duplicate this:
1673
hooks = branch.Branch.hooks['open']
1677
self._setup_stacking()
1679
def _setup_stacking(self):
1680
# configure stacking into the remote repository, by reading it from
1683
fallback_url = self.get_stacked_on_url()
1684
except (errors.NotStacked, errors.UnstackableBranchFormat,
1685
errors.UnstackableRepositoryFormat), e:
1687
# it's relative to this branch...
1688
fallback_url = urlutils.join(self.base, fallback_url)
1689
transports = [self.bzrdir.root_transport]
1690
if self._real_branch is not None:
1691
# The real repository is setup already:
1692
transports.append(self._real_branch._transport)
1693
self.repository.add_fallback_repository(
1694
self.repository._real_repository._fallback_repositories[0])
1696
stacked_on = branch.Branch.open(fallback_url,
1697
possible_transports=transports)
1698
self.repository.add_fallback_repository(stacked_on.repository)
1700
def _get_real_transport(self):
1701
# if we try vfs access, return the real branch's vfs transport
1703
return self._real_branch._transport
1705
_transport = property(_get_real_transport)
1708
return "%s(%s)" % (self.__class__.__name__, self.base)
1712
def _ensure_real(self):
1713
"""Ensure that there is a _real_branch set.
1715
Used before calls to self._real_branch.
1717
if self._real_branch is None:
1718
if not vfs.vfs_enabled():
1719
raise AssertionError('smart server vfs must be enabled '
1720
'to use vfs implementation')
1721
self.bzrdir._ensure_real()
1722
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1723
if self.repository._real_repository is None:
1724
# Give the remote repository the matching real repo.
1725
real_repo = self._real_branch.repository
1726
if isinstance(real_repo, RemoteRepository):
1727
real_repo._ensure_real()
1728
real_repo = real_repo._real_repository
1729
self.repository._set_real_repository(real_repo)
1730
# Give the real branch the remote repository to let fast-pathing
1732
self._real_branch.repository = self.repository
1733
if self._lock_mode == 'r':
1734
self._real_branch.lock_read()
1735
elif self._lock_mode == 'w':
1736
self._real_branch.lock_write(token=self._lock_token)
1738
def _translate_error(self, err, **context):
1739
self.repository._translate_error(err, branch=self, **context)
1741
def _clear_cached_state(self):
1742
super(RemoteBranch, self)._clear_cached_state()
1743
if self._real_branch is not None:
1744
self._real_branch._clear_cached_state()
1746
def _clear_cached_state_of_remote_branch_only(self):
1747
"""Like _clear_cached_state, but doesn't clear the cache of
1750
This is useful when falling back to calling a method of
1751
self._real_branch that changes state. In that case the underlying
1752
branch changes, so we need to invalidate this RemoteBranch's cache of
1753
it. However, there's no need to invalidate the _real_branch's cache
1754
too, in fact doing so might harm performance.
1756
super(RemoteBranch, self)._clear_cached_state()
1759
def control_files(self):
1760
# Defer actually creating RemoteBranchLockableFiles until its needed,
1761
# because it triggers an _ensure_real that we otherwise might not need.
1762
if self._control_files is None:
1763
self._control_files = RemoteBranchLockableFiles(
1764
self.bzrdir, self._client)
1765
return self._control_files
1767
def _get_checkout_format(self):
1769
return self._real_branch._get_checkout_format()
1771
def get_physical_lock_status(self):
1772
"""See Branch.get_physical_lock_status()."""
1773
# should be an API call to the server, as branches must be lockable.
1775
return self._real_branch.get_physical_lock_status()
1777
def get_stacked_on_url(self):
1778
"""Get the URL this branch is stacked against.
1780
:raises NotStacked: If the branch is not stacked.
1781
:raises UnstackableBranchFormat: If the branch does not support
1783
:raises UnstackableRepositoryFormat: If the repository does not support
1787
# there may not be a repository yet, so we can't use
1788
# self._translate_error, so we can't use self._call either.
1789
response = self._client.call('Branch.get_stacked_on_url',
1790
self._remote_path())
1791
except errors.ErrorFromSmartServer, err:
1792
# there may not be a repository yet, so we can't call through
1793
# its _translate_error
1794
_translate_error(err, branch=self)
1795
except errors.UnknownSmartMethod, err:
1797
return self._real_branch.get_stacked_on_url()
1798
if response[0] != 'ok':
1799
raise errors.UnexpectedSmartServerResponse(response)
1802
def lock_read(self):
1803
self.repository.lock_read()
1804
if not self._lock_mode:
1805
self._lock_mode = 'r'
1806
self._lock_count = 1
1807
if self._real_branch is not None:
1808
self._real_branch.lock_read()
1810
self._lock_count += 1
1812
def _remote_lock_write(self, token):
1814
branch_token = repo_token = ''
1816
branch_token = token
1817
repo_token = self.repository.lock_write()
1818
self.repository.unlock()
1819
err_context = {'token': token}
1820
response = self._call(
1821
'Branch.lock_write', self._remote_path(), branch_token,
1822
repo_token or '', **err_context)
1823
if response[0] != 'ok':
1824
raise errors.UnexpectedSmartServerResponse(response)
1825
ok, branch_token, repo_token = response
1826
return branch_token, repo_token
1828
def lock_write(self, token=None):
1829
if not self._lock_mode:
1830
# Lock the branch and repo in one remote call.
1831
remote_tokens = self._remote_lock_write(token)
1832
self._lock_token, self._repo_lock_token = remote_tokens
1833
if not self._lock_token:
1834
raise SmartProtocolError('Remote server did not return a token!')
1835
# Tell the self.repository object that it is locked.
1836
self.repository.lock_write(
1837
self._repo_lock_token, _skip_rpc=True)
1839
if self._real_branch is not None:
1840
self._real_branch.lock_write(token=self._lock_token)
1841
if token is not None:
1842
self._leave_lock = True
1844
self._leave_lock = False
1845
self._lock_mode = 'w'
1846
self._lock_count = 1
1847
elif self._lock_mode == 'r':
1848
raise errors.ReadOnlyTransaction
1850
if token is not None:
1851
# A token was given to lock_write, and we're relocking, so
1852
# check that the given token actually matches the one we
1854
if token != self._lock_token:
1855
raise errors.TokenMismatch(token, self._lock_token)
1856
self._lock_count += 1
1857
# Re-lock the repository too.
1858
self.repository.lock_write(self._repo_lock_token)
1859
return self._lock_token or None
1861
def _unlock(self, branch_token, repo_token):
1862
err_context = {'token': str((branch_token, repo_token))}
1863
response = self._call(
1864
'Branch.unlock', self._remote_path(), branch_token,
1865
repo_token or '', **err_context)
1866
if response == ('ok',):
1868
raise errors.UnexpectedSmartServerResponse(response)
1872
self._lock_count -= 1
1873
if not self._lock_count:
1874
self._clear_cached_state()
1875
mode = self._lock_mode
1876
self._lock_mode = None
1877
if self._real_branch is not None:
1878
if (not self._leave_lock and mode == 'w' and
1879
self._repo_lock_token):
1880
# If this RemoteBranch will remove the physical lock
1881
# for the repository, make sure the _real_branch
1882
# doesn't do it first. (Because the _real_branch's
1883
# repository is set to be the RemoteRepository.)
1884
self._real_branch.repository.leave_lock_in_place()
1885
self._real_branch.unlock()
1887
# Only write-locked branched need to make a remote method
1888
# call to perfom the unlock.
1890
if not self._lock_token:
1891
raise AssertionError('Locked, but no token!')
1892
branch_token = self._lock_token
1893
repo_token = self._repo_lock_token
1894
self._lock_token = None
1895
self._repo_lock_token = None
1896
if not self._leave_lock:
1897
self._unlock(branch_token, repo_token)
1899
self.repository.unlock()
1901
def break_lock(self):
1903
return self._real_branch.break_lock()
1905
def leave_lock_in_place(self):
1906
if not self._lock_token:
1907
raise NotImplementedError(self.leave_lock_in_place)
1908
self._leave_lock = True
1910
def dont_leave_lock_in_place(self):
1911
if not self._lock_token:
1912
raise NotImplementedError(self.dont_leave_lock_in_place)
1913
self._leave_lock = False
1915
def _last_revision_info(self):
1916
response = self._call('Branch.last_revision_info', self._remote_path())
1917
if response[0] != 'ok':
1918
raise SmartProtocolError('unexpected response code %s' % (response,))
1919
revno = int(response[1])
1920
last_revision = response[2]
1921
return (revno, last_revision)
1923
def _gen_revision_history(self):
1924
"""See Branch._gen_revision_history()."""
1925
response_tuple, response_handler = self._call_expecting_body(
1926
'Branch.revision_history', self._remote_path())
1927
if response_tuple[0] != 'ok':
1928
raise errors.UnexpectedSmartServerResponse(response_tuple)
1929
result = response_handler.read_body_bytes().split('\x00')
1934
def _remote_path(self):
1935
return self.bzrdir._path_for_remote_call(self._client)
1937
def _set_last_revision_descendant(self, revision_id, other_branch,
1938
allow_diverged=False, allow_overwrite_descendant=False):
1939
# This performs additional work to meet the hook contract; while its
1940
# undesirable, we have to synthesise the revno to call the hook, and
1941
# not calling the hook is worse as it means changes can't be prevented.
1942
# Having calculated this though, we can't just call into
1943
# set_last_revision_info as a simple call, because there is a set_rh
1944
# hook that some folk may still be using.
1945
old_revno, old_revid = self.last_revision_info()
1946
history = self._lefthand_history(revision_id)
1947
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1948
err_context = {'other_branch': other_branch}
1949
response = self._call('Branch.set_last_revision_ex',
1950
self._remote_path(), self._lock_token, self._repo_lock_token,
1951
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1953
self._clear_cached_state()
1954
if len(response) != 3 and response[0] != 'ok':
1955
raise errors.UnexpectedSmartServerResponse(response)
1956
new_revno, new_revision_id = response[1:]
1957
self._last_revision_info_cache = new_revno, new_revision_id
1958
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1959
if self._real_branch is not None:
1960
cache = new_revno, new_revision_id
1961
self._real_branch._last_revision_info_cache = cache
1963
def _set_last_revision(self, revision_id):
1964
old_revno, old_revid = self.last_revision_info()
1965
# This performs additional work to meet the hook contract; while its
1966
# undesirable, we have to synthesise the revno to call the hook, and
1967
# not calling the hook is worse as it means changes can't be prevented.
1968
# Having calculated this though, we can't just call into
1969
# set_last_revision_info as a simple call, because there is a set_rh
1970
# hook that some folk may still be using.
1971
history = self._lefthand_history(revision_id)
1972
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1973
self._clear_cached_state()
1974
response = self._call('Branch.set_last_revision',
1975
self._remote_path(), self._lock_token, self._repo_lock_token,
1977
if response != ('ok',):
1978
raise errors.UnexpectedSmartServerResponse(response)
1979
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1982
def set_revision_history(self, rev_history):
1983
# Send just the tip revision of the history; the server will generate
1984
# the full history from that. If the revision doesn't exist in this
1985
# branch, NoSuchRevision will be raised.
1986
if rev_history == []:
1989
rev_id = rev_history[-1]
1990
self._set_last_revision(rev_id)
1991
for hook in branch.Branch.hooks['set_rh']:
1992
hook(self, rev_history)
1993
self._cache_revision_history(rev_history)
1995
def get_parent(self):
1997
return self._real_branch.get_parent()
1999
def _get_parent_location(self):
2000
# Used by tests, when checking normalisation of given vs stored paths.
2002
return self._real_branch._get_parent_location()
2004
def set_parent(self, url):
2006
return self._real_branch.set_parent(url)
2008
def _set_parent_location(self, url):
2009
# Used by tests, to poke bad urls into branch configurations
2011
self.set_parent(url)
2014
return self._real_branch._set_parent_location(url)
2016
def set_stacked_on_url(self, stacked_location):
2017
"""Set the URL this branch is stacked against.
2019
:raises UnstackableBranchFormat: If the branch does not support
2021
:raises UnstackableRepositoryFormat: If the repository does not support
2025
return self._real_branch.set_stacked_on_url(stacked_location)
2028
def pull(self, source, overwrite=False, stop_revision=None,
2030
self._clear_cached_state_of_remote_branch_only()
2032
return self._real_branch.pull(
2033
source, overwrite=overwrite, stop_revision=stop_revision,
2034
_override_hook_target=self, **kwargs)
2037
def push(self, target, overwrite=False, stop_revision=None):
2039
return self._real_branch.push(
2040
target, overwrite=overwrite, stop_revision=stop_revision,
2041
_override_hook_source_branch=self)
2043
def is_locked(self):
2044
return self._lock_count >= 1
2047
def revision_id_to_revno(self, revision_id):
2049
return self._real_branch.revision_id_to_revno(revision_id)
2052
def set_last_revision_info(self, revno, revision_id):
2053
# XXX: These should be returned by the set_last_revision_info verb
2054
old_revno, old_revid = self.last_revision_info()
2055
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2056
revision_id = ensure_null(revision_id)
2058
response = self._call('Branch.set_last_revision_info',
2059
self._remote_path(), self._lock_token, self._repo_lock_token,
2060
str(revno), revision_id)
2061
except errors.UnknownSmartMethod:
2063
self._clear_cached_state_of_remote_branch_only()
2064
self._real_branch.set_last_revision_info(revno, revision_id)
2065
self._last_revision_info_cache = revno, revision_id
2067
if response == ('ok',):
2068
self._clear_cached_state()
2069
self._last_revision_info_cache = revno, revision_id
2070
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2071
# Update the _real_branch's cache too.
2072
if self._real_branch is not None:
2073
cache = self._last_revision_info_cache
2074
self._real_branch._last_revision_info_cache = cache
2076
raise errors.UnexpectedSmartServerResponse(response)
2079
def generate_revision_history(self, revision_id, last_rev=None,
2081
medium = self._client._medium
2082
if not medium._is_remote_before((1, 6)):
2083
# Use a smart method for 1.6 and above servers
2085
self._set_last_revision_descendant(revision_id, other_branch,
2086
allow_diverged=True, allow_overwrite_descendant=True)
2088
except errors.UnknownSmartMethod:
2089
medium._remember_remote_is_before((1, 6))
2090
self._clear_cached_state_of_remote_branch_only()
2091
self.set_revision_history(self._lefthand_history(revision_id,
2092
last_rev=last_rev,other_branch=other_branch))
2097
return self._real_branch.tags
2099
def set_push_location(self, location):
2101
return self._real_branch.set_push_location(location)
2104
def _extract_tar(tar, to_dir):
2105
"""Extract all the contents of a tarfile object.
2107
A replacement for extractall, which is not present in python2.4
2110
tar.extract(tarinfo, to_dir)
2113
def _translate_error(err, **context):
2114
"""Translate an ErrorFromSmartServer into a more useful error.
2116
Possible context keys:
2124
If the error from the server doesn't match a known pattern, then
2125
UnknownErrorFromSmartServer is raised.
2129
return context[name]
2130
except KeyError, key_err:
2131
mutter('Missing key %r in context %r', key_err.args[0], context)
2134
"""Get the path from the context if present, otherwise use first error
2138
return context['path']
2139
except KeyError, key_err:
2141
return err.error_args[0]
2142
except IndexError, idx_err:
2144
'Missing key %r in context %r', key_err.args[0], context)
2147
if err.error_verb == 'NoSuchRevision':
2148
raise NoSuchRevision(find('branch'), err.error_args[0])
2149
elif err.error_verb == 'nosuchrevision':
2150
raise NoSuchRevision(find('repository'), err.error_args[0])
2151
elif err.error_tuple == ('nobranch',):
2152
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2153
elif err.error_verb == 'norepository':
2154
raise errors.NoRepositoryPresent(find('bzrdir'))
2155
elif err.error_verb == 'LockContention':
2156
raise errors.LockContention('(remote lock)')
2157
elif err.error_verb == 'UnlockableTransport':
2158
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2159
elif err.error_verb == 'LockFailed':
2160
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2161
elif err.error_verb == 'TokenMismatch':
2162
raise errors.TokenMismatch(find('token'), '(remote token)')
2163
elif err.error_verb == 'Diverged':
2164
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2165
elif err.error_verb == 'TipChangeRejected':
2166
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2167
elif err.error_verb == 'UnstackableBranchFormat':
2168
raise errors.UnstackableBranchFormat(*err.error_args)
2169
elif err.error_verb == 'UnstackableRepositoryFormat':
2170
raise errors.UnstackableRepositoryFormat(*err.error_args)
2171
elif err.error_verb == 'NotStacked':
2172
raise errors.NotStacked(branch=find('branch'))
2173
elif err.error_verb == 'PermissionDenied':
2175
if len(err.error_args) >= 2:
2176
extra = err.error_args[1]
2179
raise errors.PermissionDenied(path, extra=extra)
2180
elif err.error_verb == 'ReadError':
2182
raise errors.ReadError(path)
2183
elif err.error_verb == 'NoSuchFile':
2185
raise errors.NoSuchFile(path)
2186
elif err.error_verb == 'FileExists':
2187
raise errors.FileExists(err.error_args[0])
2188
elif err.error_verb == 'DirectoryNotEmpty':
2189
raise errors.DirectoryNotEmpty(err.error_args[0])
2190
elif err.error_verb == 'ShortReadvError':
2191
args = err.error_args
2192
raise errors.ShortReadvError(
2193
args[0], int(args[1]), int(args[2]), int(args[3]))
2194
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2195
encoding = str(err.error_args[0]) # encoding must always be a string
2196
val = err.error_args[1]
2197
start = int(err.error_args[2])
2198
end = int(err.error_args[3])
2199
reason = str(err.error_args[4]) # reason must always be a string
2200
if val.startswith('u:'):
2201
val = val[2:].decode('utf-8')
2202
elif val.startswith('s:'):
2203
val = val[2:].decode('base64')
2204
if err.error_verb == 'UnicodeDecodeError':
2205
raise UnicodeDecodeError(encoding, val, start, end, reason)
2206
elif err.error_verb == 'UnicodeEncodeError':
2207
raise UnicodeEncodeError(encoding, val, start, end, reason)
2208
elif err.error_verb == 'ReadOnlyError':
2209
raise errors.TransportNotPossible('readonly transport')
2210
raise errors.UnknownErrorFromSmartServer(err)