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
# ICK: perhaps change these registries to be factories only?
159
format = bzrdir.network_format_registry.get(control_name).__class__()
161
format.repository_format = repository.network_format_registry.get(
164
format.set_branch_format(
165
branch.network_format_registry.get(branch_name))
168
def create_repository(self, shared=False):
169
# as per meta1 formats - just delegate to the format object which may
171
result = self._format.repository_format.initialize(self, shared)
172
if not isinstance(result, RemoteRepository):
173
return self.open_repository()
177
def destroy_repository(self):
178
"""See BzrDir.destroy_repository"""
180
self._real_bzrdir.destroy_repository()
182
def create_branch(self):
183
# as per meta1 formats - just delegate to the format object which may
185
real_branch = self._format.get_branch_format().initialize(self)
186
if not isinstance(real_branch, RemoteBranch):
187
result = RemoteBranch(self, self.find_repository(), real_branch)
190
# BzrDir.clone_on_transport() uses the result of create_branch but does
191
# not return it to its callers; we save approximately 8% of our round
192
# trips by handing the branch we created back to the first caller to
193
# open_branch rather than probing anew. Long term we need a API in
194
# bzrdir that doesn't discard result objects (like result_branch).
196
self._next_open_branch_result = result
199
def destroy_branch(self):
200
"""See BzrDir.destroy_branch"""
202
self._real_bzrdir.destroy_branch()
203
self._next_open_branch_result = None
205
def create_workingtree(self, revision_id=None, from_branch=None):
206
raise errors.NotLocalUrl(self.transport.base)
208
def find_branch_format(self):
209
"""Find the branch 'format' for this bzrdir.
211
This might be a synthetic object for e.g. RemoteBranch and SVN.
213
b = self.open_branch()
216
def get_branch_reference(self):
217
"""See BzrDir.get_branch_reference()."""
218
path = self._path_for_remote_call(self._client)
219
response = self._call('BzrDir.open_branch', path)
220
if response[0] == 'ok':
221
if response[1] == '':
222
# branch at this location.
225
# a branch reference, use the existing BranchReference logic.
228
raise errors.UnexpectedSmartServerResponse(response)
230
def _get_tree_branch(self):
231
"""See BzrDir._get_tree_branch()."""
232
return None, self.open_branch()
234
def open_branch(self, _unsupported=False):
236
raise NotImplementedError('unsupported flag support not implemented yet.')
237
if self._next_open_branch_result is not None:
238
# See create_branch for details.
239
result = self._next_open_branch_result
240
self._next_open_branch_result = None
242
reference_url = self.get_branch_reference()
243
if reference_url is None:
244
# branch at this location.
245
return RemoteBranch(self, self.find_repository())
247
# a branch reference, use the existing BranchReference logic.
248
format = BranchReferenceFormat()
249
return format.open(self, _found=True, location=reference_url)
251
def _open_repo_v1(self, path):
252
verb = 'BzrDir.find_repository'
253
response = self._call(verb, path)
254
if response[0] != 'ok':
255
raise errors.UnexpectedSmartServerResponse(response)
256
# servers that only support the v1 method don't support external
259
repo = self._real_bzrdir.open_repository()
260
response = response + ('no', repo._format.network_name())
261
return response, repo
263
def _open_repo_v2(self, path):
264
verb = 'BzrDir.find_repositoryV2'
265
response = self._call(verb, path)
266
if response[0] != 'ok':
267
raise errors.UnexpectedSmartServerResponse(response)
269
repo = self._real_bzrdir.open_repository()
270
response = response + (repo._format.network_name(),)
271
return response, repo
273
def _open_repo_v3(self, path):
274
verb = 'BzrDir.find_repositoryV3'
275
medium = self._client._medium
276
if medium._is_remote_before((1, 13)):
277
raise errors.UnknownSmartMethod(verb)
278
response = self._call(verb, path)
279
if response[0] != 'ok':
280
raise errors.UnexpectedSmartServerResponse(response)
281
return response, None
283
def open_repository(self):
284
path = self._path_for_remote_call(self._client)
286
for probe in [self._open_repo_v3, self._open_repo_v2,
289
response, real_repo = probe(path)
291
except errors.UnknownSmartMethod:
294
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
295
if response[0] != 'ok':
296
raise errors.UnexpectedSmartServerResponse(response)
297
if len(response) != 6:
298
raise SmartProtocolError('incorrect response length %s' % (response,))
299
if response[1] == '':
300
# repo is at this dir.
301
format = response_tuple_to_repo_format(response[2:])
302
# Used to support creating a real format instance when needed.
303
format._creating_bzrdir = self
304
remote_repo = RemoteRepository(self, format)
305
format._creating_repo = remote_repo
306
if real_repo is not None:
307
remote_repo._set_real_repository(real_repo)
310
raise errors.NoRepositoryPresent(self)
312
def open_workingtree(self, recommend_upgrade=True):
314
if self._real_bzrdir.has_workingtree():
315
raise errors.NotLocalUrl(self.root_transport)
317
raise errors.NoWorkingTree(self.root_transport.base)
319
def _path_for_remote_call(self, client):
320
"""Return the path to be used for this bzrdir in a remote call."""
321
return client.remote_path_from_transport(self.root_transport)
323
def get_branch_transport(self, branch_format):
325
return self._real_bzrdir.get_branch_transport(branch_format)
327
def get_repository_transport(self, repository_format):
329
return self._real_bzrdir.get_repository_transport(repository_format)
331
def get_workingtree_transport(self, workingtree_format):
333
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
335
def can_convert_format(self):
336
"""Upgrading of remote bzrdirs is not supported yet."""
339
def needs_format_conversion(self, format=None):
340
"""Upgrading of remote bzrdirs is not supported yet."""
342
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
343
% 'needs_format_conversion(format=None)')
346
def clone(self, url, revision_id=None, force_new_repo=False,
347
preserve_stacking=False):
349
return self._real_bzrdir.clone(url, revision_id=revision_id,
350
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
352
def get_config(self):
354
return self._real_bzrdir.get_config()
357
class RemoteRepositoryFormat(repository.RepositoryFormat):
358
"""Format for repositories accessed over a _SmartClient.
360
Instances of this repository are represented by RemoteRepository
363
The RemoteRepositoryFormat is parameterized during construction
364
to reflect the capabilities of the real, remote format. Specifically
365
the attributes rich_root_data and supports_tree_reference are set
366
on a per instance basis, and are not set (and should not be) at
369
:ivar _custom_format: If set, a specific concrete repository format that
370
will be used when initializing a repository with this
371
RemoteRepositoryFormat.
372
:ivar _creating_repo: If set, the repository object that this
373
RemoteRepositoryFormat was created for: it can be called into
374
to obtain data like the network name.
377
_matchingbzrdir = RemoteBzrDirFormat()
380
repository.RepositoryFormat.__init__(self)
381
self._custom_format = None
382
self._network_name = None
383
self._creating_bzrdir = None
385
def _vfs_initialize(self, a_bzrdir, shared):
386
"""Helper for common code in initialize."""
387
if self._custom_format:
388
# Custom format requested
389
result = self._custom_format.initialize(a_bzrdir, shared=shared)
390
elif self._creating_bzrdir is not None:
391
# Use the format that the repository we were created to back
393
prior_repo = self._creating_bzrdir.open_repository()
394
prior_repo._ensure_real()
395
result = prior_repo._real_repository._format.initialize(
396
a_bzrdir, shared=shared)
398
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
399
# support remote initialization.
400
# We delegate to a real object at this point (as RemoteBzrDir
401
# delegate to the repository format which would lead to infinite
402
# recursion if we just called a_bzrdir.create_repository.
403
a_bzrdir._ensure_real()
404
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
405
if not isinstance(result, RemoteRepository):
406
return self.open(a_bzrdir)
410
def initialize(self, a_bzrdir, shared=False):
411
# Being asked to create on a non RemoteBzrDir:
412
if not isinstance(a_bzrdir, RemoteBzrDir):
413
return self._vfs_initialize(a_bzrdir, shared)
414
medium = a_bzrdir._client._medium
415
if medium._is_remote_before((1, 13)):
416
return self._vfs_initialize(a_bzrdir, shared)
417
# Creating on a remote bzr dir.
418
# 1) get the network name to use.
419
if self._custom_format:
420
network_name = self._custom_format.network_name()
422
# Select the current bzrlib default and ask for that.
423
reference_bzrdir_format = bzrdir.format_registry.get('default')()
424
reference_format = reference_bzrdir_format.repository_format
425
network_name = reference_format.network_name()
426
# 2) try direct creation via RPC
427
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
428
verb = 'BzrDir.create_repository'
434
response = a_bzrdir._call(verb, path, network_name, shared_str)
435
except errors.UnknownSmartMethod:
436
# Fallback - use vfs methods
437
return self._vfs_initialize(a_bzrdir, shared)
439
# Turn the response into a RemoteRepository object.
440
format = response_tuple_to_repo_format(response[1:])
441
# Used to support creating a real format instance when needed.
442
format._creating_bzrdir = a_bzrdir
443
remote_repo = RemoteRepository(a_bzrdir, format)
444
format._creating_repo = remote_repo
447
def open(self, a_bzrdir):
448
if not isinstance(a_bzrdir, RemoteBzrDir):
449
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
450
return a_bzrdir.open_repository()
452
def _ensure_real(self):
453
if self._custom_format is None:
454
self._custom_format = repository.network_format_registry.get(
458
def _fetch_order(self):
460
return self._custom_format._fetch_order
463
def _fetch_uses_deltas(self):
465
return self._custom_format._fetch_uses_deltas
468
def _fetch_reconcile(self):
470
return self._custom_format._fetch_reconcile
472
def get_format_description(self):
473
return 'bzr remote repository'
475
def __eq__(self, other):
476
return self.__class__ == other.__class__
478
def check_conversion_target(self, target_format):
479
if self.rich_root_data and not target_format.rich_root_data:
480
raise errors.BadConversionTarget(
481
'Does not support rich root data.', target_format)
482
if (self.supports_tree_reference and
483
not getattr(target_format, 'supports_tree_reference', False)):
484
raise errors.BadConversionTarget(
485
'Does not support nested trees', target_format)
487
def network_name(self):
488
if self._network_name:
489
return self._network_name
490
self._creating_repo._ensure_real()
491
return self._creating_repo._real_repository._format.network_name()
494
def _serializer(self):
496
return self._custom_format._serializer
499
class RemoteRepository(_RpcHelper):
500
"""Repository accessed over rpc.
502
For the moment most operations are performed using local transport-backed
506
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
507
"""Create a RemoteRepository instance.
509
:param remote_bzrdir: The bzrdir hosting this repository.
510
:param format: The RemoteFormat object to use.
511
:param real_repository: If not None, a local implementation of the
512
repository logic for the repository, usually accessing the data
514
:param _client: Private testing parameter - override the smart client
515
to be used by the repository.
518
self._real_repository = real_repository
520
self._real_repository = None
521
self.bzrdir = remote_bzrdir
523
self._client = remote_bzrdir._client
525
self._client = _client
526
self._format = format
527
self._lock_mode = None
528
self._lock_token = None
530
self._leave_lock = False
531
self._unstacked_provider = graph.CachingParentsProvider(
532
get_parent_map=self._get_parent_map_rpc)
533
self._unstacked_provider.disable_cache()
535
# These depend on the actual remote format, so force them off for
536
# maximum compatibility. XXX: In future these should depend on the
537
# remote repository instance, but this is irrelevant until we perform
538
# reconcile via an RPC call.
539
self._reconcile_does_inventory_gc = False
540
self._reconcile_fixes_text_parents = False
541
self._reconcile_backsup_inventory = False
542
self.base = self.bzrdir.transport.base
543
# Additional places to query for data.
544
self._fallback_repositories = []
547
return "%s(%s)" % (self.__class__.__name__, self.base)
551
def abort_write_group(self, suppress_errors=False):
552
"""Complete a write group on the decorated repository.
554
Smart methods peform operations in a single step so this api
555
is not really applicable except as a compatibility thunk
556
for older plugins that don't use e.g. the CommitBuilder
559
:param suppress_errors: see Repository.abort_write_group.
562
return self._real_repository.abort_write_group(
563
suppress_errors=suppress_errors)
565
def commit_write_group(self):
566
"""Complete a write group on the decorated repository.
568
Smart methods peform operations in a single step so this api
569
is not really applicable except as a compatibility thunk
570
for older plugins that don't use e.g. the CommitBuilder
574
return self._real_repository.commit_write_group()
576
def resume_write_group(self, tokens):
578
return self._real_repository.resume_write_group(tokens)
580
def suspend_write_group(self):
582
return self._real_repository.suspend_write_group()
584
def _ensure_real(self):
585
"""Ensure that there is a _real_repository set.
587
Used before calls to self._real_repository.
589
if self._real_repository is None:
590
self.bzrdir._ensure_real()
591
self._set_real_repository(
592
self.bzrdir._real_bzrdir.open_repository())
594
def _translate_error(self, err, **context):
595
self.bzrdir._translate_error(err, repository=self, **context)
597
def find_text_key_references(self):
598
"""Find the text key references within the repository.
600
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
601
revision_ids. Each altered file-ids has the exact revision_ids that
602
altered it listed explicitly.
603
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
604
to whether they were referred to by the inventory of the
605
revision_id that they contain. The inventory texts from all present
606
revision ids are assessed to generate this report.
609
return self._real_repository.find_text_key_references()
611
def _generate_text_key_index(self):
612
"""Generate a new text key index for the repository.
614
This is an expensive function that will take considerable time to run.
616
:return: A dict mapping (file_id, revision_id) tuples to a list of
617
parents, also (file_id, revision_id) tuples.
620
return self._real_repository._generate_text_key_index()
622
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
623
def get_revision_graph(self, revision_id=None):
624
"""See Repository.get_revision_graph()."""
625
return self._get_revision_graph(revision_id)
627
def _get_revision_graph(self, revision_id):
628
"""Private method for using with old (< 1.2) servers to fallback."""
629
if revision_id is None:
631
elif revision.is_null(revision_id):
634
path = self.bzrdir._path_for_remote_call(self._client)
635
response = self._call_expecting_body(
636
'Repository.get_revision_graph', path, revision_id)
637
response_tuple, response_handler = response
638
if response_tuple[0] != 'ok':
639
raise errors.UnexpectedSmartServerResponse(response_tuple)
640
coded = response_handler.read_body_bytes()
642
# no revisions in this repository!
644
lines = coded.split('\n')
647
d = tuple(line.split())
648
revision_graph[d[0]] = d[1:]
650
return revision_graph
653
"""See Repository._get_sink()."""
654
return RemoteStreamSink(self)
656
def _get_source(self, to_format):
657
"""Return a source for streaming from this repository."""
658
return RemoteStreamSource(self, to_format)
660
def has_revision(self, revision_id):
661
"""See Repository.has_revision()."""
662
if revision_id == NULL_REVISION:
663
# The null revision is always present.
665
path = self.bzrdir._path_for_remote_call(self._client)
666
response = self._call('Repository.has_revision', path, revision_id)
667
if response[0] not in ('yes', 'no'):
668
raise errors.UnexpectedSmartServerResponse(response)
669
if response[0] == 'yes':
671
for fallback_repo in self._fallback_repositories:
672
if fallback_repo.has_revision(revision_id):
676
def has_revisions(self, revision_ids):
677
"""See Repository.has_revisions()."""
678
# FIXME: This does many roundtrips, particularly when there are
679
# fallback repositories. -- mbp 20080905
681
for revision_id in revision_ids:
682
if self.has_revision(revision_id):
683
result.add(revision_id)
686
def has_same_location(self, other):
687
return (self.__class__ == other.__class__ and
688
self.bzrdir.transport.base == other.bzrdir.transport.base)
690
def get_graph(self, other_repository=None):
691
"""Return the graph for this repository format"""
692
parents_provider = self._make_parents_provider(other_repository)
693
return graph.Graph(parents_provider)
695
def gather_stats(self, revid=None, committers=None):
696
"""See Repository.gather_stats()."""
697
path = self.bzrdir._path_for_remote_call(self._client)
698
# revid can be None to indicate no revisions, not just NULL_REVISION
699
if revid is None or revision.is_null(revid):
703
if committers is None or not committers:
704
fmt_committers = 'no'
706
fmt_committers = 'yes'
707
response_tuple, response_handler = self._call_expecting_body(
708
'Repository.gather_stats', path, fmt_revid, fmt_committers)
709
if response_tuple[0] != 'ok':
710
raise errors.UnexpectedSmartServerResponse(response_tuple)
712
body = response_handler.read_body_bytes()
714
for line in body.split('\n'):
717
key, val_text = line.split(':')
718
if key in ('revisions', 'size', 'committers'):
719
result[key] = int(val_text)
720
elif key in ('firstrev', 'latestrev'):
721
values = val_text.split(' ')[1:]
722
result[key] = (float(values[0]), long(values[1]))
726
def find_branches(self, using=False):
727
"""See Repository.find_branches()."""
728
# should be an API call to the server.
730
return self._real_repository.find_branches(using=using)
732
def get_physical_lock_status(self):
733
"""See Repository.get_physical_lock_status()."""
734
# should be an API call to the server.
736
return self._real_repository.get_physical_lock_status()
738
def is_in_write_group(self):
739
"""Return True if there is an open write group.
741
write groups are only applicable locally for the smart server..
743
if self._real_repository:
744
return self._real_repository.is_in_write_group()
747
return self._lock_count >= 1
750
"""See Repository.is_shared()."""
751
path = self.bzrdir._path_for_remote_call(self._client)
752
response = self._call('Repository.is_shared', path)
753
if response[0] not in ('yes', 'no'):
754
raise SmartProtocolError('unexpected response code %s' % (response,))
755
return response[0] == 'yes'
757
def is_write_locked(self):
758
return self._lock_mode == 'w'
761
# wrong eventually - want a local lock cache context
762
if not self._lock_mode:
763
self._lock_mode = 'r'
765
self._unstacked_provider.enable_cache(cache_misses=False)
766
if self._real_repository is not None:
767
self._real_repository.lock_read()
769
self._lock_count += 1
771
def _remote_lock_write(self, token):
772
path = self.bzrdir._path_for_remote_call(self._client)
775
err_context = {'token': token}
776
response = self._call('Repository.lock_write', path, token,
778
if response[0] == 'ok':
782
raise errors.UnexpectedSmartServerResponse(response)
784
def lock_write(self, token=None, _skip_rpc=False):
785
if not self._lock_mode:
787
if self._lock_token is not None:
788
if token != self._lock_token:
789
raise errors.TokenMismatch(token, self._lock_token)
790
self._lock_token = token
792
self._lock_token = self._remote_lock_write(token)
793
# if self._lock_token is None, then this is something like packs or
794
# svn where we don't get to lock the repo, or a weave style repository
795
# where we cannot lock it over the wire and attempts to do so will
797
if self._real_repository is not None:
798
self._real_repository.lock_write(token=self._lock_token)
799
if token is not None:
800
self._leave_lock = True
802
self._leave_lock = False
803
self._lock_mode = 'w'
805
self._unstacked_provider.enable_cache(cache_misses=False)
806
elif self._lock_mode == 'r':
807
raise errors.ReadOnlyError(self)
809
self._lock_count += 1
810
return self._lock_token or None
812
def leave_lock_in_place(self):
813
if not self._lock_token:
814
raise NotImplementedError(self.leave_lock_in_place)
815
self._leave_lock = True
817
def dont_leave_lock_in_place(self):
818
if not self._lock_token:
819
raise NotImplementedError(self.dont_leave_lock_in_place)
820
self._leave_lock = False
822
def _set_real_repository(self, repository):
823
"""Set the _real_repository for this repository.
825
:param repository: The repository to fallback to for non-hpss
826
implemented operations.
828
if self._real_repository is not None:
829
# Replacing an already set real repository.
830
# We cannot do this [currently] if the repository is locked -
831
# synchronised state might be lost.
833
raise AssertionError('_real_repository is already set')
834
if isinstance(repository, RemoteRepository):
835
raise AssertionError()
836
self._real_repository = repository
837
for fb in self._fallback_repositories:
838
self._real_repository.add_fallback_repository(fb)
839
if self._lock_mode == 'w':
840
# if we are already locked, the real repository must be able to
841
# acquire the lock with our token.
842
self._real_repository.lock_write(self._lock_token)
843
elif self._lock_mode == 'r':
844
self._real_repository.lock_read()
846
def start_write_group(self):
847
"""Start a write group on the decorated repository.
849
Smart methods peform operations in a single step so this api
850
is not really applicable except as a compatibility thunk
851
for older plugins that don't use e.g. the CommitBuilder
855
return self._real_repository.start_write_group()
857
def _unlock(self, token):
858
path = self.bzrdir._path_for_remote_call(self._client)
860
# with no token the remote repository is not persistently locked.
862
err_context = {'token': token}
863
response = self._call('Repository.unlock', path, token,
865
if response == ('ok',):
868
raise errors.UnexpectedSmartServerResponse(response)
871
if not self._lock_count:
872
raise errors.LockNotHeld(self)
873
self._lock_count -= 1
874
if self._lock_count > 0:
876
self._unstacked_provider.disable_cache()
877
old_mode = self._lock_mode
878
self._lock_mode = None
880
# The real repository is responsible at present for raising an
881
# exception if it's in an unfinished write group. However, it
882
# normally will *not* actually remove the lock from disk - that's
883
# done by the server on receiving the Repository.unlock call.
884
# This is just to let the _real_repository stay up to date.
885
if self._real_repository is not None:
886
self._real_repository.unlock()
888
# The rpc-level lock should be released even if there was a
889
# problem releasing the vfs-based lock.
891
# Only write-locked repositories need to make a remote method
892
# call to perfom the unlock.
893
old_token = self._lock_token
894
self._lock_token = None
895
if not self._leave_lock:
896
self._unlock(old_token)
898
def break_lock(self):
899
# should hand off to the network
901
return self._real_repository.break_lock()
903
def _get_tarball(self, compression):
904
"""Return a TemporaryFile containing a repository tarball.
906
Returns None if the server does not support sending tarballs.
909
path = self.bzrdir._path_for_remote_call(self._client)
911
response, protocol = self._call_expecting_body(
912
'Repository.tarball', path, compression)
913
except errors.UnknownSmartMethod:
914
protocol.cancel_read_body()
916
if response[0] == 'ok':
917
# Extract the tarball and return it
918
t = tempfile.NamedTemporaryFile()
919
# TODO: rpc layer should read directly into it...
920
t.write(protocol.read_body_bytes())
923
raise errors.UnexpectedSmartServerResponse(response)
925
def sprout(self, to_bzrdir, revision_id=None):
926
# TODO: Option to control what format is created?
928
dest_repo = self._real_repository._format.initialize(to_bzrdir,
930
dest_repo.fetch(self, revision_id=revision_id)
933
### These methods are just thin shims to the VFS object for now.
935
def revision_tree(self, revision_id):
937
return self._real_repository.revision_tree(revision_id)
939
def get_serializer_format(self):
941
return self._real_repository.get_serializer_format()
943
def get_commit_builder(self, branch, parents, config, timestamp=None,
944
timezone=None, committer=None, revprops=None,
946
# FIXME: It ought to be possible to call this without immediately
947
# triggering _ensure_real. For now it's the easiest thing to do.
949
real_repo = self._real_repository
950
builder = real_repo.get_commit_builder(branch, parents,
951
config, timestamp=timestamp, timezone=timezone,
952
committer=committer, revprops=revprops, revision_id=revision_id)
955
def add_fallback_repository(self, repository):
956
"""Add a repository to use for looking up data not held locally.
958
:param repository: A repository.
960
# XXX: At the moment the RemoteRepository will allow fallbacks
961
# unconditionally - however, a _real_repository will usually exist,
962
# and may raise an error if it's not accommodated by the underlying
963
# format. Eventually we should check when opening the repository
964
# whether it's willing to allow them or not.
966
# We need to accumulate additional repositories here, to pass them in
969
self._fallback_repositories.append(repository)
970
# If self._real_repository was parameterised already (e.g. because a
971
# _real_branch had its get_stacked_on_url method called), then the
972
# repository to be added may already be in the _real_repositories list.
973
if self._real_repository is not None:
974
if repository not in self._real_repository._fallback_repositories:
975
self._real_repository.add_fallback_repository(repository)
977
# They are also seen by the fallback repository. If it doesn't
978
# exist yet they'll be added then. This implicitly copies them.
981
def add_inventory(self, revid, inv, parents):
983
return self._real_repository.add_inventory(revid, inv, parents)
985
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
988
return self._real_repository.add_inventory_by_delta(basis_revision_id,
989
delta, new_revision_id, parents)
991
def add_revision(self, rev_id, rev, inv=None, config=None):
993
return self._real_repository.add_revision(
994
rev_id, rev, inv=inv, config=config)
997
def get_inventory(self, revision_id):
999
return self._real_repository.get_inventory(revision_id)
1001
def iter_inventories(self, revision_ids):
1003
return self._real_repository.iter_inventories(revision_ids)
1006
def get_revision(self, revision_id):
1008
return self._real_repository.get_revision(revision_id)
1010
def get_transaction(self):
1012
return self._real_repository.get_transaction()
1015
def clone(self, a_bzrdir, revision_id=None):
1017
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1019
def make_working_trees(self):
1020
"""See Repository.make_working_trees"""
1022
return self._real_repository.make_working_trees()
1024
def revision_ids_to_search_result(self, result_set):
1025
"""Convert a set of revision ids to a graph SearchResult."""
1026
result_parents = set()
1027
for parents in self.get_graph().get_parent_map(
1028
result_set).itervalues():
1029
result_parents.update(parents)
1030
included_keys = result_set.intersection(result_parents)
1031
start_keys = result_set.difference(included_keys)
1032
exclude_keys = result_parents.difference(result_set)
1033
result = graph.SearchResult(start_keys, exclude_keys,
1034
len(result_set), result_set)
1038
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1039
"""Return the revision ids that other has that this does not.
1041
These are returned in topological order.
1043
revision_id: only return revision ids included by revision_id.
1045
return repository.InterRepository.get(
1046
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1048
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
1049
# Not delegated to _real_repository so that InterRepository.get has a
1050
# chance to find an InterRepository specialised for RemoteRepository.
1051
if self.has_same_location(source):
1052
# check that last_revision is in 'from' and then return a
1054
if (revision_id is not None and
1055
not revision.is_null(revision_id)):
1056
self.get_revision(revision_id)
1058
inter = repository.InterRepository.get(source, self)
1060
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
1061
except NotImplementedError:
1062
raise errors.IncompatibleRepositories(source, self)
1064
def create_bundle(self, target, base, fileobj, format=None):
1066
self._real_repository.create_bundle(target, base, fileobj, format)
1069
def get_ancestry(self, revision_id, topo_sorted=True):
1071
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1073
def fileids_altered_by_revision_ids(self, revision_ids):
1075
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1077
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1079
return self._real_repository._get_versioned_file_checker(
1080
revisions, revision_versions_cache)
1082
def iter_files_bytes(self, desired_files):
1083
"""See Repository.iter_file_bytes.
1086
return self._real_repository.iter_files_bytes(desired_files)
1088
def get_parent_map(self, revision_ids):
1089
"""See bzrlib.Graph.get_parent_map()."""
1090
return self._make_parents_provider().get_parent_map(revision_ids)
1092
def _get_parent_map_rpc(self, keys):
1093
"""Helper for get_parent_map that performs the RPC."""
1094
medium = self._client._medium
1095
if medium._is_remote_before((1, 2)):
1096
# We already found out that the server can't understand
1097
# Repository.get_parent_map requests, so just fetch the whole
1099
# XXX: Note that this will issue a deprecation warning. This is ok
1100
# :- its because we're working with a deprecated server anyway, and
1101
# the user will almost certainly have seen a warning about the
1102
# server version already.
1103
rg = self.get_revision_graph()
1104
# There is an api discrepency between get_parent_map and
1105
# get_revision_graph. Specifically, a "key:()" pair in
1106
# get_revision_graph just means a node has no parents. For
1107
# "get_parent_map" it means the node is a ghost. So fix up the
1108
# graph to correct this.
1109
# https://bugs.launchpad.net/bzr/+bug/214894
1110
# There is one other "bug" which is that ghosts in
1111
# get_revision_graph() are not returned at all. But we won't worry
1112
# about that for now.
1113
for node_id, parent_ids in rg.iteritems():
1114
if parent_ids == ():
1115
rg[node_id] = (NULL_REVISION,)
1116
rg[NULL_REVISION] = ()
1121
raise ValueError('get_parent_map(None) is not valid')
1122
if NULL_REVISION in keys:
1123
keys.discard(NULL_REVISION)
1124
found_parents = {NULL_REVISION:()}
1126
return found_parents
1129
# TODO(Needs analysis): We could assume that the keys being requested
1130
# from get_parent_map are in a breadth first search, so typically they
1131
# will all be depth N from some common parent, and we don't have to
1132
# have the server iterate from the root parent, but rather from the
1133
# keys we're searching; and just tell the server the keyspace we
1134
# already have; but this may be more traffic again.
1136
# Transform self._parents_map into a search request recipe.
1137
# TODO: Manage this incrementally to avoid covering the same path
1138
# repeatedly. (The server will have to on each request, but the less
1139
# work done the better).
1140
parents_map = self._unstacked_provider.get_cached_map()
1141
if parents_map is None:
1142
# Repository is not locked, so there's no cache.
1144
start_set = set(parents_map)
1145
result_parents = set()
1146
for parents in parents_map.itervalues():
1147
result_parents.update(parents)
1148
stop_keys = result_parents.difference(start_set)
1149
included_keys = start_set.intersection(result_parents)
1150
start_set.difference_update(included_keys)
1151
recipe = (start_set, stop_keys, len(parents_map))
1152
body = self._serialise_search_recipe(recipe)
1153
path = self.bzrdir._path_for_remote_call(self._client)
1155
if type(key) is not str:
1157
"key %r not a plain string" % (key,))
1158
verb = 'Repository.get_parent_map'
1159
args = (path,) + tuple(keys)
1161
response = self._call_with_body_bytes_expecting_body(
1163
except errors.UnknownSmartMethod:
1164
# Server does not support this method, so get the whole graph.
1165
# Worse, we have to force a disconnection, because the server now
1166
# doesn't realise it has a body on the wire to consume, so the
1167
# only way to recover is to abandon the connection.
1169
'Server is too old for fast get_parent_map, reconnecting. '
1170
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1172
# To avoid having to disconnect repeatedly, we keep track of the
1173
# fact the server doesn't understand remote methods added in 1.2.
1174
medium._remember_remote_is_before((1, 2))
1175
return self.get_revision_graph(None)
1176
response_tuple, response_handler = response
1177
if response_tuple[0] not in ['ok']:
1178
response_handler.cancel_read_body()
1179
raise errors.UnexpectedSmartServerResponse(response_tuple)
1180
if response_tuple[0] == 'ok':
1181
coded = bz2.decompress(response_handler.read_body_bytes())
1183
# no revisions found
1185
lines = coded.split('\n')
1188
d = tuple(line.split())
1190
revision_graph[d[0]] = d[1:]
1192
# No parents - so give the Graph result (NULL_REVISION,).
1193
revision_graph[d[0]] = (NULL_REVISION,)
1194
return revision_graph
1197
def get_signature_text(self, revision_id):
1199
return self._real_repository.get_signature_text(revision_id)
1202
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1203
def get_revision_graph_with_ghosts(self, revision_ids=None):
1205
return self._real_repository.get_revision_graph_with_ghosts(
1206
revision_ids=revision_ids)
1209
def get_inventory_xml(self, revision_id):
1211
return self._real_repository.get_inventory_xml(revision_id)
1213
def deserialise_inventory(self, revision_id, xml):
1215
return self._real_repository.deserialise_inventory(revision_id, xml)
1217
def reconcile(self, other=None, thorough=False):
1219
return self._real_repository.reconcile(other=other, thorough=thorough)
1221
def all_revision_ids(self):
1223
return self._real_repository.all_revision_ids()
1226
def get_deltas_for_revisions(self, revisions):
1228
return self._real_repository.get_deltas_for_revisions(revisions)
1231
def get_revision_delta(self, revision_id):
1233
return self._real_repository.get_revision_delta(revision_id)
1236
def revision_trees(self, revision_ids):
1238
return self._real_repository.revision_trees(revision_ids)
1241
def get_revision_reconcile(self, revision_id):
1243
return self._real_repository.get_revision_reconcile(revision_id)
1246
def check(self, revision_ids=None):
1248
return self._real_repository.check(revision_ids=revision_ids)
1250
def copy_content_into(self, destination, revision_id=None):
1252
return self._real_repository.copy_content_into(
1253
destination, revision_id=revision_id)
1255
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1256
# get a tarball of the remote repository, and copy from that into the
1258
from bzrlib import osutils
1260
# TODO: Maybe a progress bar while streaming the tarball?
1261
note("Copying repository content as tarball...")
1262
tar_file = self._get_tarball('bz2')
1263
if tar_file is None:
1265
destination = to_bzrdir.create_repository()
1267
tar = tarfile.open('repository', fileobj=tar_file,
1269
tmpdir = osutils.mkdtemp()
1271
_extract_tar(tar, tmpdir)
1272
tmp_bzrdir = BzrDir.open(tmpdir)
1273
tmp_repo = tmp_bzrdir.open_repository()
1274
tmp_repo.copy_content_into(destination, revision_id)
1276
osutils.rmtree(tmpdir)
1280
# TODO: Suggestion from john: using external tar is much faster than
1281
# python's tarfile library, but it may not work on windows.
1284
def inventories(self):
1285
"""Decorate the real repository for now.
1287
In the long term a full blown network facility is needed to
1288
avoid creating a real repository object locally.
1291
return self._real_repository.inventories
1295
"""Compress the data within the repository.
1297
This is not currently implemented within the smart server.
1300
return self._real_repository.pack()
1303
def revisions(self):
1304
"""Decorate the real repository for now.
1306
In the short term this should become a real object to intercept graph
1309
In the long term a full blown network facility is needed.
1312
return self._real_repository.revisions
1314
def set_make_working_trees(self, new_value):
1316
new_value_str = "True"
1318
new_value_str = "False"
1319
path = self.bzrdir._path_for_remote_call(self._client)
1321
response = self._call(
1322
'Repository.set_make_working_trees', path, new_value_str)
1323
except errors.UnknownSmartMethod:
1325
self._real_repository.set_make_working_trees(new_value)
1327
if response[0] != 'ok':
1328
raise errors.UnexpectedSmartServerResponse(response)
1331
def signatures(self):
1332
"""Decorate the real repository for now.
1334
In the long term a full blown network facility is needed to avoid
1335
creating a real repository object locally.
1338
return self._real_repository.signatures
1341
def sign_revision(self, revision_id, gpg_strategy):
1343
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1347
"""Decorate the real repository for now.
1349
In the long term a full blown network facility is needed to avoid
1350
creating a real repository object locally.
1353
return self._real_repository.texts
1356
def get_revisions(self, revision_ids):
1358
return self._real_repository.get_revisions(revision_ids)
1360
def supports_rich_root(self):
1361
return self._format.rich_root_data
1363
def iter_reverse_revision_history(self, revision_id):
1365
return self._real_repository.iter_reverse_revision_history(revision_id)
1368
def _serializer(self):
1369
return self._format._serializer
1371
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1373
return self._real_repository.store_revision_signature(
1374
gpg_strategy, plaintext, revision_id)
1376
def add_signature_text(self, revision_id, signature):
1378
return self._real_repository.add_signature_text(revision_id, signature)
1380
def has_signature_for_revision_id(self, revision_id):
1382
return self._real_repository.has_signature_for_revision_id(revision_id)
1384
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1386
return self._real_repository.item_keys_introduced_by(revision_ids,
1387
_files_pb=_files_pb)
1389
def revision_graph_can_have_wrong_parents(self):
1390
# The answer depends on the remote repo format.
1392
return self._real_repository.revision_graph_can_have_wrong_parents()
1394
def _find_inconsistent_revision_parents(self):
1396
return self._real_repository._find_inconsistent_revision_parents()
1398
def _check_for_inconsistent_revision_parents(self):
1400
return self._real_repository._check_for_inconsistent_revision_parents()
1402
def _make_parents_provider(self, other=None):
1403
providers = [self._unstacked_provider]
1404
if other is not None:
1405
providers.insert(0, other)
1406
providers.extend(r._make_parents_provider() for r in
1407
self._fallback_repositories)
1408
return graph._StackedParentsProvider(providers)
1410
def _serialise_search_recipe(self, recipe):
1411
"""Serialise a graph search recipe.
1413
:param recipe: A search recipe (start, stop, count).
1414
:return: Serialised bytes.
1416
start_keys = ' '.join(recipe[0])
1417
stop_keys = ' '.join(recipe[1])
1418
count = str(recipe[2])
1419
return '\n'.join((start_keys, stop_keys, count))
1422
path = self.bzrdir._path_for_remote_call(self._client)
1424
response = self._call('PackRepository.autopack', path)
1425
except errors.UnknownSmartMethod:
1427
self._real_repository._pack_collection.autopack()
1429
if self._real_repository is not None:
1430
# Reset the real repository's cache of pack names.
1431
# XXX: At some point we may be able to skip this and just rely on
1432
# the automatic retry logic to do the right thing, but for now we
1433
# err on the side of being correct rather than being optimal.
1434
self._real_repository._pack_collection.reload_pack_names()
1435
if response[0] != 'ok':
1436
raise errors.UnexpectedSmartServerResponse(response)
1439
class RemoteStreamSink(repository.StreamSink):
1441
def _insert_real(self, stream, src_format, resume_tokens):
1442
self.target_repo._ensure_real()
1443
sink = self.target_repo._real_repository._get_sink()
1444
result = sink.insert_stream(stream, src_format, resume_tokens)
1446
self.target_repo.autopack()
1449
def insert_stream(self, stream, src_format, resume_tokens):
1450
repo = self.target_repo
1451
client = repo._client
1452
medium = client._medium
1453
if medium._is_remote_before((1, 13)):
1454
# No possible way this can work.
1455
return self._insert_real(stream, src_format, resume_tokens)
1456
path = repo.bzrdir._path_for_remote_call(client)
1457
if not resume_tokens:
1458
# XXX: Ugly but important for correctness, *will* be fixed during
1459
# 1.13 cycle. Pushing a stream that is interrupted results in a
1460
# fallback to the _real_repositories sink *with a partial stream*.
1461
# Thats bad because we insert less data than bzr expected. To avoid
1462
# this we do a trial push to make sure the verb is accessible, and
1463
# do not fallback when actually pushing the stream. A cleanup patch
1464
# is going to look at rewinding/restarting the stream/partial
1466
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1468
response = client.call_with_body_stream(
1469
('Repository.insert_stream', path, ''), byte_stream)
1470
except errors.UnknownSmartMethod:
1471
medium._remember_remote_is_before((1,13))
1472
return self._insert_real(stream, src_format, resume_tokens)
1473
byte_stream = smart_repo._stream_to_byte_stream(
1475
resume_tokens = ' '.join(resume_tokens)
1476
response = client.call_with_body_stream(
1477
('Repository.insert_stream', path, resume_tokens), byte_stream)
1478
if response[0][0] not in ('ok', 'missing-basis'):
1479
raise errors.UnexpectedSmartServerResponse(response)
1480
if response[0][0] == 'missing-basis':
1481
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1482
resume_tokens = tokens
1483
return resume_tokens, missing_keys
1485
if self.target_repo._real_repository is not None:
1486
collection = getattr(self.target_repo._real_repository,
1487
'_pack_collection', None)
1488
if collection is not None:
1489
collection.reload_pack_names()
1493
class RemoteStreamSource(repository.StreamSource):
1494
"""Stream data from a remote server."""
1496
def get_stream(self, search):
1497
# streaming with fallback repositories is not well defined yet: The
1498
# remote repository cannot see the fallback repositories, and thus
1499
# cannot satisfy the entire search in the general case. Likewise the
1500
# fallback repositories cannot reify the search to determine what they
1501
# should send. It likely needs a return value in the stream listing the
1502
# edge of the search to resume from in fallback repositories.
1503
if self.from_repository._fallback_repositories:
1504
return repository.StreamSource.get_stream(self, search)
1505
repo = self.from_repository
1506
client = repo._client
1507
medium = client._medium
1508
if medium._is_remote_before((1, 13)):
1509
# No possible way this can work.
1510
return repository.StreamSource.get_stream(self, search)
1511
path = repo.bzrdir._path_for_remote_call(client)
1513
recipe = repo._serialise_search_recipe(search._recipe)
1514
response = repo._call_with_body_bytes_expecting_body(
1515
'Repository.get_stream',
1516
(path, self.to_format.network_name()), recipe)
1517
response_tuple, response_handler = response
1518
except errors.UnknownSmartMethod:
1519
medium._remember_remote_is_before((1,13))
1520
return repository.StreamSource.get_stream(self, search)
1521
if response_tuple[0] != 'ok':
1522
raise errors.UnexpectedSmartServerResponse(response_tuple)
1523
byte_stream = response_handler.read_streamed_body()
1524
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1525
if src_format.network_name() != repo._format.network_name():
1526
raise AssertionError(
1527
"Mismatched RemoteRepository and stream src %r, %r" % (
1528
src_format.network_name(), repo._format.network_name()))
1532
class RemoteBranchLockableFiles(LockableFiles):
1533
"""A 'LockableFiles' implementation that talks to a smart server.
1535
This is not a public interface class.
1538
def __init__(self, bzrdir, _client):
1539
self.bzrdir = bzrdir
1540
self._client = _client
1541
self._need_find_modes = True
1542
LockableFiles.__init__(
1543
self, bzrdir.get_branch_transport(None),
1544
'lock', lockdir.LockDir)
1546
def _find_modes(self):
1547
# RemoteBranches don't let the client set the mode of control files.
1548
self._dir_mode = None
1549
self._file_mode = None
1552
class RemoteBranchFormat(branch.BranchFormat):
1555
super(RemoteBranchFormat, self).__init__()
1556
self._matchingbzrdir = RemoteBzrDirFormat()
1557
self._matchingbzrdir.set_branch_format(self)
1558
self._custom_format = None
1560
def __eq__(self, other):
1561
return (isinstance(other, RemoteBranchFormat) and
1562
self.__dict__ == other.__dict__)
1564
def get_format_description(self):
1565
return 'Remote BZR Branch'
1567
def network_name(self):
1568
return self._network_name
1570
def open(self, a_bzrdir):
1571
return a_bzrdir.open_branch()
1573
def _vfs_initialize(self, a_bzrdir):
1574
# Initialisation when using a local bzrdir object, or a non-vfs init
1575
# method is not available on the server.
1576
# self._custom_format is always set - the start of initialize ensures
1578
if isinstance(a_bzrdir, RemoteBzrDir):
1579
a_bzrdir._ensure_real()
1580
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1582
# We assume the bzrdir is parameterised; it may not be.
1583
result = self._custom_format.initialize(a_bzrdir)
1584
if (isinstance(a_bzrdir, RemoteBzrDir) and
1585
not isinstance(result, RemoteBranch)):
1586
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1589
def initialize(self, a_bzrdir):
1590
# 1) get the network name to use.
1591
if self._custom_format:
1592
network_name = self._custom_format.network_name()
1594
# Select the current bzrlib default and ask for that.
1595
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1596
reference_format = reference_bzrdir_format.get_branch_format()
1597
self._custom_format = reference_format
1598
network_name = reference_format.network_name()
1599
# Being asked to create on a non RemoteBzrDir:
1600
if not isinstance(a_bzrdir, RemoteBzrDir):
1601
return self._vfs_initialize(a_bzrdir)
1602
medium = a_bzrdir._client._medium
1603
if medium._is_remote_before((1, 13)):
1604
return self._vfs_initialize(a_bzrdir)
1605
# Creating on a remote bzr dir.
1606
# 2) try direct creation via RPC
1607
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1608
verb = 'BzrDir.create_branch'
1610
response = a_bzrdir._call(verb, path, network_name)
1611
except errors.UnknownSmartMethod:
1612
# Fallback - use vfs methods
1613
return self._vfs_initialize(a_bzrdir)
1614
if response[0] != 'ok':
1615
raise errors.UnexpectedSmartServerResponse(response)
1616
# Turn the response into a RemoteRepository object.
1617
format = RemoteBranchFormat()
1618
format._network_name = response[1]
1619
repo_format = response_tuple_to_repo_format(response[3:])
1620
if response[2] == '':
1621
repo_bzrdir = a_bzrdir
1623
repo_bzrdir = RemoteBzrDir(
1624
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1626
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1627
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1628
format=format, setup_stacking=False)
1629
# XXX: We know this is a new branch, so it must have revno 0, revid
1630
# NULL_REVISION. Creating the branch locked would make this be unable
1631
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1632
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1633
return remote_branch
1635
def supports_tags(self):
1636
# Remote branches might support tags, but we won't know until we
1637
# access the real remote branch.
1641
class RemoteBranch(branch.Branch, _RpcHelper):
1642
"""Branch stored on a server accessed by HPSS RPC.
1644
At the moment most operations are mapped down to simple file operations.
1647
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1648
_client=None, format=None, setup_stacking=True):
1649
"""Create a RemoteBranch instance.
1651
:param real_branch: An optional local implementation of the branch
1652
format, usually accessing the data via the VFS.
1653
:param _client: Private parameter for testing.
1654
:param format: A RemoteBranchFormat object, None to create one
1655
automatically. If supplied it should have a network_name already
1657
:param setup_stacking: If True make an RPC call to determine the
1658
stacked (or not) status of the branch. If False assume the branch
1661
# We intentionally don't call the parent class's __init__, because it
1662
# will try to assign to self.tags, which is a property in this subclass.
1663
# And the parent's __init__ doesn't do much anyway.
1664
self._revision_id_to_revno_cache = None
1665
self._partial_revision_id_to_revno_cache = {}
1666
self._revision_history_cache = None
1667
self._last_revision_info_cache = None
1668
self._merge_sorted_revisions_cache = None
1669
self.bzrdir = remote_bzrdir
1670
if _client is not None:
1671
self._client = _client
1673
self._client = remote_bzrdir._client
1674
self.repository = remote_repository
1675
if real_branch is not None:
1676
self._real_branch = real_branch
1677
# Give the remote repository the matching real repo.
1678
real_repo = self._real_branch.repository
1679
if isinstance(real_repo, RemoteRepository):
1680
real_repo._ensure_real()
1681
real_repo = real_repo._real_repository
1682
self.repository._set_real_repository(real_repo)
1683
# Give the branch the remote repository to let fast-pathing happen.
1684
self._real_branch.repository = self.repository
1686
self._real_branch = None
1687
# Fill out expected attributes of branch for bzrlib api users.
1688
self.base = self.bzrdir.root_transport.base
1689
self._control_files = None
1690
self._lock_mode = None
1691
self._lock_token = None
1692
self._repo_lock_token = None
1693
self._lock_count = 0
1694
self._leave_lock = False
1695
# Setup a format: note that we cannot call _ensure_real until all the
1696
# attributes above are set: This code cannot be moved higher up in this
1699
self._format = RemoteBranchFormat()
1700
if real_branch is not None:
1701
self._format._network_name = \
1702
self._real_branch._format.network_name()
1704
# # XXX: Need to get this from BzrDir.open_branch's return value.
1705
# self._ensure_real()
1706
# self._format._network_name = \
1707
# self._real_branch._format.network_name()
1709
self._format = format
1710
# The base class init is not called, so we duplicate this:
1711
hooks = branch.Branch.hooks['open']
1715
self._setup_stacking()
1717
def _setup_stacking(self):
1718
# configure stacking into the remote repository, by reading it from
1721
fallback_url = self.get_stacked_on_url()
1722
except (errors.NotStacked, errors.UnstackableBranchFormat,
1723
errors.UnstackableRepositoryFormat), e:
1725
# it's relative to this branch...
1726
fallback_url = urlutils.join(self.base, fallback_url)
1727
transports = [self.bzrdir.root_transport]
1728
if self._real_branch is not None:
1729
# The real repository is setup already:
1730
transports.append(self._real_branch._transport)
1731
self.repository.add_fallback_repository(
1732
self.repository._real_repository._fallback_repositories[0])
1734
stacked_on = branch.Branch.open(fallback_url,
1735
possible_transports=transports)
1736
self.repository.add_fallback_repository(stacked_on.repository)
1738
def _get_real_transport(self):
1739
# if we try vfs access, return the real branch's vfs transport
1741
return self._real_branch._transport
1743
_transport = property(_get_real_transport)
1746
return "%s(%s)" % (self.__class__.__name__, self.base)
1750
def _ensure_real(self):
1751
"""Ensure that there is a _real_branch set.
1753
Used before calls to self._real_branch.
1755
if self._real_branch is None:
1756
if not vfs.vfs_enabled():
1757
raise AssertionError('smart server vfs must be enabled '
1758
'to use vfs implementation')
1759
self.bzrdir._ensure_real()
1760
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1761
if self.repository._real_repository is None:
1762
# Give the remote repository the matching real repo.
1763
real_repo = self._real_branch.repository
1764
if isinstance(real_repo, RemoteRepository):
1765
real_repo._ensure_real()
1766
real_repo = real_repo._real_repository
1767
self.repository._set_real_repository(real_repo)
1768
# Give the real branch the remote repository to let fast-pathing
1770
self._real_branch.repository = self.repository
1771
if self._lock_mode == 'r':
1772
self._real_branch.lock_read()
1773
elif self._lock_mode == 'w':
1774
self._real_branch.lock_write(token=self._lock_token)
1776
def _translate_error(self, err, **context):
1777
self.repository._translate_error(err, branch=self, **context)
1779
def _clear_cached_state(self):
1780
super(RemoteBranch, self)._clear_cached_state()
1781
if self._real_branch is not None:
1782
self._real_branch._clear_cached_state()
1784
def _clear_cached_state_of_remote_branch_only(self):
1785
"""Like _clear_cached_state, but doesn't clear the cache of
1788
This is useful when falling back to calling a method of
1789
self._real_branch that changes state. In that case the underlying
1790
branch changes, so we need to invalidate this RemoteBranch's cache of
1791
it. However, there's no need to invalidate the _real_branch's cache
1792
too, in fact doing so might harm performance.
1794
super(RemoteBranch, self)._clear_cached_state()
1797
def control_files(self):
1798
# Defer actually creating RemoteBranchLockableFiles until its needed,
1799
# because it triggers an _ensure_real that we otherwise might not need.
1800
if self._control_files is None:
1801
self._control_files = RemoteBranchLockableFiles(
1802
self.bzrdir, self._client)
1803
return self._control_files
1805
def _get_checkout_format(self):
1807
return self._real_branch._get_checkout_format()
1809
def get_physical_lock_status(self):
1810
"""See Branch.get_physical_lock_status()."""
1811
# should be an API call to the server, as branches must be lockable.
1813
return self._real_branch.get_physical_lock_status()
1815
def get_stacked_on_url(self):
1816
"""Get the URL this branch is stacked against.
1818
:raises NotStacked: If the branch is not stacked.
1819
:raises UnstackableBranchFormat: If the branch does not support
1821
:raises UnstackableRepositoryFormat: If the repository does not support
1825
# there may not be a repository yet, so we can't use
1826
# self._translate_error, so we can't use self._call either.
1827
response = self._client.call('Branch.get_stacked_on_url',
1828
self._remote_path())
1829
except errors.ErrorFromSmartServer, err:
1830
# there may not be a repository yet, so we can't call through
1831
# its _translate_error
1832
_translate_error(err, branch=self)
1833
except errors.UnknownSmartMethod, err:
1835
return self._real_branch.get_stacked_on_url()
1836
if response[0] != 'ok':
1837
raise errors.UnexpectedSmartServerResponse(response)
1840
def lock_read(self):
1841
self.repository.lock_read()
1842
if not self._lock_mode:
1843
self._lock_mode = 'r'
1844
self._lock_count = 1
1845
if self._real_branch is not None:
1846
self._real_branch.lock_read()
1848
self._lock_count += 1
1850
def _remote_lock_write(self, token):
1852
branch_token = repo_token = ''
1854
branch_token = token
1855
repo_token = self.repository.lock_write()
1856
self.repository.unlock()
1857
err_context = {'token': token}
1858
response = self._call(
1859
'Branch.lock_write', self._remote_path(), branch_token,
1860
repo_token or '', **err_context)
1861
if response[0] != 'ok':
1862
raise errors.UnexpectedSmartServerResponse(response)
1863
ok, branch_token, repo_token = response
1864
return branch_token, repo_token
1866
def lock_write(self, token=None):
1867
if not self._lock_mode:
1868
# Lock the branch and repo in one remote call.
1869
remote_tokens = self._remote_lock_write(token)
1870
self._lock_token, self._repo_lock_token = remote_tokens
1871
if not self._lock_token:
1872
raise SmartProtocolError('Remote server did not return a token!')
1873
# Tell the self.repository object that it is locked.
1874
self.repository.lock_write(
1875
self._repo_lock_token, _skip_rpc=True)
1877
if self._real_branch is not None:
1878
self._real_branch.lock_write(token=self._lock_token)
1879
if token is not None:
1880
self._leave_lock = True
1882
self._leave_lock = False
1883
self._lock_mode = 'w'
1884
self._lock_count = 1
1885
elif self._lock_mode == 'r':
1886
raise errors.ReadOnlyTransaction
1888
if token is not None:
1889
# A token was given to lock_write, and we're relocking, so
1890
# check that the given token actually matches the one we
1892
if token != self._lock_token:
1893
raise errors.TokenMismatch(token, self._lock_token)
1894
self._lock_count += 1
1895
# Re-lock the repository too.
1896
self.repository.lock_write(self._repo_lock_token)
1897
return self._lock_token or None
1899
def _unlock(self, branch_token, repo_token):
1900
err_context = {'token': str((branch_token, repo_token))}
1901
response = self._call(
1902
'Branch.unlock', self._remote_path(), branch_token,
1903
repo_token or '', **err_context)
1904
if response == ('ok',):
1906
raise errors.UnexpectedSmartServerResponse(response)
1910
self._lock_count -= 1
1911
if not self._lock_count:
1912
self._clear_cached_state()
1913
mode = self._lock_mode
1914
self._lock_mode = None
1915
if self._real_branch is not None:
1916
if (not self._leave_lock and mode == 'w' and
1917
self._repo_lock_token):
1918
# If this RemoteBranch will remove the physical lock
1919
# for the repository, make sure the _real_branch
1920
# doesn't do it first. (Because the _real_branch's
1921
# repository is set to be the RemoteRepository.)
1922
self._real_branch.repository.leave_lock_in_place()
1923
self._real_branch.unlock()
1925
# Only write-locked branched need to make a remote method
1926
# call to perfom the unlock.
1928
if not self._lock_token:
1929
raise AssertionError('Locked, but no token!')
1930
branch_token = self._lock_token
1931
repo_token = self._repo_lock_token
1932
self._lock_token = None
1933
self._repo_lock_token = None
1934
if not self._leave_lock:
1935
self._unlock(branch_token, repo_token)
1937
self.repository.unlock()
1939
def break_lock(self):
1941
return self._real_branch.break_lock()
1943
def leave_lock_in_place(self):
1944
if not self._lock_token:
1945
raise NotImplementedError(self.leave_lock_in_place)
1946
self._leave_lock = True
1948
def dont_leave_lock_in_place(self):
1949
if not self._lock_token:
1950
raise NotImplementedError(self.dont_leave_lock_in_place)
1951
self._leave_lock = False
1953
def _last_revision_info(self):
1954
response = self._call('Branch.last_revision_info', self._remote_path())
1955
if response[0] != 'ok':
1956
raise SmartProtocolError('unexpected response code %s' % (response,))
1957
revno = int(response[1])
1958
last_revision = response[2]
1959
return (revno, last_revision)
1961
def _gen_revision_history(self):
1962
"""See Branch._gen_revision_history()."""
1963
response_tuple, response_handler = self._call_expecting_body(
1964
'Branch.revision_history', self._remote_path())
1965
if response_tuple[0] != 'ok':
1966
raise errors.UnexpectedSmartServerResponse(response_tuple)
1967
result = response_handler.read_body_bytes().split('\x00')
1972
def _remote_path(self):
1973
return self.bzrdir._path_for_remote_call(self._client)
1975
def _set_last_revision_descendant(self, revision_id, other_branch,
1976
allow_diverged=False, allow_overwrite_descendant=False):
1977
# This performs additional work to meet the hook contract; while its
1978
# undesirable, we have to synthesise the revno to call the hook, and
1979
# not calling the hook is worse as it means changes can't be prevented.
1980
# Having calculated this though, we can't just call into
1981
# set_last_revision_info as a simple call, because there is a set_rh
1982
# hook that some folk may still be using.
1983
old_revno, old_revid = self.last_revision_info()
1984
history = self._lefthand_history(revision_id)
1985
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1986
err_context = {'other_branch': other_branch}
1987
response = self._call('Branch.set_last_revision_ex',
1988
self._remote_path(), self._lock_token, self._repo_lock_token,
1989
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1991
self._clear_cached_state()
1992
if len(response) != 3 and response[0] != 'ok':
1993
raise errors.UnexpectedSmartServerResponse(response)
1994
new_revno, new_revision_id = response[1:]
1995
self._last_revision_info_cache = new_revno, new_revision_id
1996
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1997
if self._real_branch is not None:
1998
cache = new_revno, new_revision_id
1999
self._real_branch._last_revision_info_cache = cache
2001
def _set_last_revision(self, revision_id):
2002
old_revno, old_revid = self.last_revision_info()
2003
# This performs additional work to meet the hook contract; while its
2004
# undesirable, we have to synthesise the revno to call the hook, and
2005
# not calling the hook is worse as it means changes can't be prevented.
2006
# Having calculated this though, we can't just call into
2007
# set_last_revision_info as a simple call, because there is a set_rh
2008
# hook that some folk may still be using.
2009
history = self._lefthand_history(revision_id)
2010
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2011
self._clear_cached_state()
2012
response = self._call('Branch.set_last_revision',
2013
self._remote_path(), self._lock_token, self._repo_lock_token,
2015
if response != ('ok',):
2016
raise errors.UnexpectedSmartServerResponse(response)
2017
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2020
def set_revision_history(self, rev_history):
2021
# Send just the tip revision of the history; the server will generate
2022
# the full history from that. If the revision doesn't exist in this
2023
# branch, NoSuchRevision will be raised.
2024
if rev_history == []:
2027
rev_id = rev_history[-1]
2028
self._set_last_revision(rev_id)
2029
for hook in branch.Branch.hooks['set_rh']:
2030
hook(self, rev_history)
2031
self._cache_revision_history(rev_history)
2033
def get_parent(self):
2035
return self._real_branch.get_parent()
2037
def _get_parent_location(self):
2038
# Used by tests, when checking normalisation of given vs stored paths.
2040
return self._real_branch._get_parent_location()
2042
def set_parent(self, url):
2044
return self._real_branch.set_parent(url)
2046
def _set_parent_location(self, url):
2047
# Used by tests, to poke bad urls into branch configurations
2049
self.set_parent(url)
2052
return self._real_branch._set_parent_location(url)
2054
def set_stacked_on_url(self, stacked_location):
2055
"""Set the URL this branch is stacked against.
2057
:raises UnstackableBranchFormat: If the branch does not support
2059
:raises UnstackableRepositoryFormat: If the repository does not support
2063
return self._real_branch.set_stacked_on_url(stacked_location)
2066
def pull(self, source, overwrite=False, stop_revision=None,
2068
self._clear_cached_state_of_remote_branch_only()
2070
return self._real_branch.pull(
2071
source, overwrite=overwrite, stop_revision=stop_revision,
2072
_override_hook_target=self, **kwargs)
2075
def push(self, target, overwrite=False, stop_revision=None):
2077
return self._real_branch.push(
2078
target, overwrite=overwrite, stop_revision=stop_revision,
2079
_override_hook_source_branch=self)
2081
def is_locked(self):
2082
return self._lock_count >= 1
2085
def revision_id_to_revno(self, revision_id):
2087
return self._real_branch.revision_id_to_revno(revision_id)
2090
def set_last_revision_info(self, revno, revision_id):
2091
# XXX: These should be returned by the set_last_revision_info verb
2092
old_revno, old_revid = self.last_revision_info()
2093
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2094
revision_id = ensure_null(revision_id)
2096
response = self._call('Branch.set_last_revision_info',
2097
self._remote_path(), self._lock_token, self._repo_lock_token,
2098
str(revno), revision_id)
2099
except errors.UnknownSmartMethod:
2101
self._clear_cached_state_of_remote_branch_only()
2102
self._real_branch.set_last_revision_info(revno, revision_id)
2103
self._last_revision_info_cache = revno, revision_id
2105
if response == ('ok',):
2106
self._clear_cached_state()
2107
self._last_revision_info_cache = revno, revision_id
2108
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2109
# Update the _real_branch's cache too.
2110
if self._real_branch is not None:
2111
cache = self._last_revision_info_cache
2112
self._real_branch._last_revision_info_cache = cache
2114
raise errors.UnexpectedSmartServerResponse(response)
2117
def generate_revision_history(self, revision_id, last_rev=None,
2119
medium = self._client._medium
2120
if not medium._is_remote_before((1, 6)):
2121
# Use a smart method for 1.6 and above servers
2123
self._set_last_revision_descendant(revision_id, other_branch,
2124
allow_diverged=True, allow_overwrite_descendant=True)
2126
except errors.UnknownSmartMethod:
2127
medium._remember_remote_is_before((1, 6))
2128
self._clear_cached_state_of_remote_branch_only()
2129
self.set_revision_history(self._lefthand_history(revision_id,
2130
last_rev=last_rev,other_branch=other_branch))
2135
return self._real_branch.tags
2137
def set_push_location(self, location):
2139
return self._real_branch.set_push_location(location)
2142
def _extract_tar(tar, to_dir):
2143
"""Extract all the contents of a tarfile object.
2145
A replacement for extractall, which is not present in python2.4
2148
tar.extract(tarinfo, to_dir)
2151
def _translate_error(err, **context):
2152
"""Translate an ErrorFromSmartServer into a more useful error.
2154
Possible context keys:
2162
If the error from the server doesn't match a known pattern, then
2163
UnknownErrorFromSmartServer is raised.
2167
return context[name]
2168
except KeyError, key_err:
2169
mutter('Missing key %r in context %r', key_err.args[0], context)
2172
"""Get the path from the context if present, otherwise use first error
2176
return context['path']
2177
except KeyError, key_err:
2179
return err.error_args[0]
2180
except IndexError, idx_err:
2182
'Missing key %r in context %r', key_err.args[0], context)
2185
if err.error_verb == 'NoSuchRevision':
2186
raise NoSuchRevision(find('branch'), err.error_args[0])
2187
elif err.error_verb == 'nosuchrevision':
2188
raise NoSuchRevision(find('repository'), err.error_args[0])
2189
elif err.error_tuple == ('nobranch',):
2190
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2191
elif err.error_verb == 'norepository':
2192
raise errors.NoRepositoryPresent(find('bzrdir'))
2193
elif err.error_verb == 'LockContention':
2194
raise errors.LockContention('(remote lock)')
2195
elif err.error_verb == 'UnlockableTransport':
2196
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2197
elif err.error_verb == 'LockFailed':
2198
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2199
elif err.error_verb == 'TokenMismatch':
2200
raise errors.TokenMismatch(find('token'), '(remote token)')
2201
elif err.error_verb == 'Diverged':
2202
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2203
elif err.error_verb == 'TipChangeRejected':
2204
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2205
elif err.error_verb == 'UnstackableBranchFormat':
2206
raise errors.UnstackableBranchFormat(*err.error_args)
2207
elif err.error_verb == 'UnstackableRepositoryFormat':
2208
raise errors.UnstackableRepositoryFormat(*err.error_args)
2209
elif err.error_verb == 'NotStacked':
2210
raise errors.NotStacked(branch=find('branch'))
2211
elif err.error_verb == 'PermissionDenied':
2213
if len(err.error_args) >= 2:
2214
extra = err.error_args[1]
2217
raise errors.PermissionDenied(path, extra=extra)
2218
elif err.error_verb == 'ReadError':
2220
raise errors.ReadError(path)
2221
elif err.error_verb == 'NoSuchFile':
2223
raise errors.NoSuchFile(path)
2224
elif err.error_verb == 'FileExists':
2225
raise errors.FileExists(err.error_args[0])
2226
elif err.error_verb == 'DirectoryNotEmpty':
2227
raise errors.DirectoryNotEmpty(err.error_args[0])
2228
elif err.error_verb == 'ShortReadvError':
2229
args = err.error_args
2230
raise errors.ShortReadvError(
2231
args[0], int(args[1]), int(args[2]), int(args[3]))
2232
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2233
encoding = str(err.error_args[0]) # encoding must always be a string
2234
val = err.error_args[1]
2235
start = int(err.error_args[2])
2236
end = int(err.error_args[3])
2237
reason = str(err.error_args[4]) # reason must always be a string
2238
if val.startswith('u:'):
2239
val = val[2:].decode('utf-8')
2240
elif val.startswith('s:'):
2241
val = val[2:].decode('base64')
2242
if err.error_verb == 'UnicodeDecodeError':
2243
raise UnicodeDecodeError(encoding, val, start, end, reason)
2244
elif err.error_verb == 'UnicodeEncodeError':
2245
raise UnicodeEncodeError(encoding, val, start, end, reason)
2246
elif err.error_verb == 'ReadOnlyError':
2247
raise errors.TransportNotPossible('readonly transport')
2248
raise errors.UnknownErrorFromSmartServer(err)