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.
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
65
return self._client.call_with_body_bytes_expecting_body(
66
method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
# Note: RemoteBzrDirFormat is in bzrdir.py
72
class RemoteBzrDir(BzrDir, _RpcHelper):
73
"""Control directory on a remote server, accessed via bzr:// or similar."""
75
def __init__(self, transport, format, _client=None):
76
"""Construct a RemoteBzrDir.
78
:param _client: Private parameter for testing. Disables probing and the
81
BzrDir.__init__(self, transport, format)
82
# this object holds a delegated bzrdir that uses file-level operations
83
# to talk to the other side
84
self._real_bzrdir = None
87
medium = transport.get_smart_medium()
88
self._client = client._SmartClient(medium)
90
self._client = _client
93
path = self._path_for_remote_call(self._client)
94
response = self._call('BzrDir.open', path)
95
if response not in [('yes',), ('no',)]:
96
raise errors.UnexpectedSmartServerResponse(response)
97
if response == ('no',):
98
raise errors.NotBranchError(path=transport.base)
100
def _ensure_real(self):
101
"""Ensure that there is a _real_bzrdir set.
103
Used before calls to self._real_bzrdir.
105
if not self._real_bzrdir:
106
self._real_bzrdir = BzrDir.open_from_transport(
107
self.root_transport, _server_formats=False)
109
def _translate_error(self, err, **context):
110
_translate_error(err, bzrdir=self, **context)
112
def cloning_metadir(self, stacked=False):
114
return self._real_bzrdir.cloning_metadir(stacked)
116
def create_repository(self, shared=False):
117
# as per meta1 formats - just delegate to the format object which may
119
result = self._format.repository_format.initialize(self, shared)
120
if not isinstance(result, RemoteRepository):
121
return self.open_repository()
125
def destroy_repository(self):
126
"""See BzrDir.destroy_repository"""
128
self._real_bzrdir.destroy_repository()
130
def create_branch(self):
131
# as per meta1 formats - just delegate to the format object which may
133
real_branch = self._format.get_branch_format().initialize(self)
134
if not isinstance(real_branch, RemoteBranch):
135
return RemoteBranch(self, self.find_repository(), real_branch)
139
def destroy_branch(self):
140
"""See BzrDir.destroy_branch"""
142
self._real_bzrdir.destroy_branch()
144
def create_workingtree(self, revision_id=None, from_branch=None):
145
raise errors.NotLocalUrl(self.transport.base)
147
def find_branch_format(self):
148
"""Find the branch 'format' for this bzrdir.
150
This might be a synthetic object for e.g. RemoteBranch and SVN.
152
b = self.open_branch()
155
def get_branch_reference(self):
156
"""See BzrDir.get_branch_reference()."""
157
path = self._path_for_remote_call(self._client)
158
response = self._call('BzrDir.open_branch', path)
159
if response[0] == 'ok':
160
if response[1] == '':
161
# branch at this location.
164
# a branch reference, use the existing BranchReference logic.
167
raise errors.UnexpectedSmartServerResponse(response)
169
def _get_tree_branch(self):
170
"""See BzrDir._get_tree_branch()."""
171
return None, self.open_branch()
173
def open_branch(self, _unsupported=False):
175
raise NotImplementedError('unsupported flag support not implemented yet.')
176
reference_url = self.get_branch_reference()
177
if reference_url is None:
178
# branch at this location.
179
return RemoteBranch(self, self.find_repository())
181
# a branch reference, use the existing BranchReference logic.
182
format = BranchReferenceFormat()
183
return format.open(self, _found=True, location=reference_url)
185
def open_repository(self):
186
path = self._path_for_remote_call(self._client)
187
verb = 'BzrDir.find_repositoryV2'
189
response = self._call(verb, path)
190
except errors.UnknownSmartMethod:
191
verb = 'BzrDir.find_repository'
192
response = self._call(verb, path)
193
if response[0] != 'ok':
194
raise errors.UnexpectedSmartServerResponse(response)
195
if verb == 'BzrDir.find_repository':
196
# servers that don't support the V2 method don't support external
198
response = response + ('no', )
199
if not (len(response) == 5):
200
raise SmartProtocolError('incorrect response length %s' % (response,))
201
if response[1] == '':
202
format = RemoteRepositoryFormat()
203
format.rich_root_data = (response[2] == 'yes')
204
format.supports_tree_reference = (response[3] == 'yes')
205
# No wire format to check this yet.
206
format.supports_external_lookups = (response[4] == 'yes')
207
# Used to support creating a real format instance when needed.
208
format._creating_bzrdir = self
209
remote_repo = RemoteRepository(self, format)
210
format._creating_repo = remote_repo
213
raise errors.NoRepositoryPresent(self)
215
def open_workingtree(self, recommend_upgrade=True):
217
if self._real_bzrdir.has_workingtree():
218
raise errors.NotLocalUrl(self.root_transport)
220
raise errors.NoWorkingTree(self.root_transport.base)
222
def _path_for_remote_call(self, client):
223
"""Return the path to be used for this bzrdir in a remote call."""
224
return client.remote_path_from_transport(self.root_transport)
226
def get_branch_transport(self, branch_format):
228
return self._real_bzrdir.get_branch_transport(branch_format)
230
def get_repository_transport(self, repository_format):
232
return self._real_bzrdir.get_repository_transport(repository_format)
234
def get_workingtree_transport(self, workingtree_format):
236
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
238
def can_convert_format(self):
239
"""Upgrading of remote bzrdirs is not supported yet."""
242
def needs_format_conversion(self, format=None):
243
"""Upgrading of remote bzrdirs is not supported yet."""
245
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
246
% 'needs_format_conversion(format=None)')
249
def clone(self, url, revision_id=None, force_new_repo=False,
250
preserve_stacking=False):
252
return self._real_bzrdir.clone(url, revision_id=revision_id,
253
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
255
def get_config(self):
257
return self._real_bzrdir.get_config()
260
class RemoteRepositoryFormat(repository.RepositoryFormat):
261
"""Format for repositories accessed over a _SmartClient.
263
Instances of this repository are represented by RemoteRepository
266
The RemoteRepositoryFormat is parameterized during construction
267
to reflect the capabilities of the real, remote format. Specifically
268
the attributes rich_root_data and supports_tree_reference are set
269
on a per instance basis, and are not set (and should not be) at
272
:ivar _custom_format: If set, a specific concrete repository format that
273
will be used when initializing a repository with this
274
RemoteRepositoryFormat.
275
:ivar _creating_repo: If set, the repository object that this
276
RemoteRepositoryFormat was created for: it can be called into
277
to obtain data like the network name.
280
_matchingbzrdir = RemoteBzrDirFormat()
283
repository.RepositoryFormat.__init__(self)
284
self._custom_format = None
286
def initialize(self, a_bzrdir, shared=False):
287
if self._custom_format:
288
# This returns a custom instance - e.g. a pack repo, not a remote
290
return self._custom_format.initialize(a_bzrdir, shared=shared)
291
if not isinstance(a_bzrdir, RemoteBzrDir):
292
prior_repo = self._creating_bzrdir.open_repository()
293
prior_repo._ensure_real()
294
return prior_repo._real_repository._format.initialize(
295
a_bzrdir, shared=shared)
296
# delegate to a real object at this point (remoteBzrDir delegate to the
297
# repository format which would lead to infinite recursion).
298
a_bzrdir._ensure_real()
299
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
300
if not isinstance(result, RemoteRepository):
301
return self.open(a_bzrdir)
305
def open(self, a_bzrdir):
306
if not isinstance(a_bzrdir, RemoteBzrDir):
307
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
308
return a_bzrdir.open_repository()
310
def get_format_description(self):
311
return 'bzr remote repository'
313
def __eq__(self, other):
314
return self.__class__ == other.__class__
316
def check_conversion_target(self, target_format):
317
if self.rich_root_data and not target_format.rich_root_data:
318
raise errors.BadConversionTarget(
319
'Does not support rich root data.', target_format)
320
if (self.supports_tree_reference and
321
not getattr(target_format, 'supports_tree_reference', False)):
322
raise errors.BadConversionTarget(
323
'Does not support nested trees', target_format)
325
def network_name(self):
326
self._creating_repo._ensure_real()
327
return self._creating_repo._real_repository._format.network_name()
330
def _serializer(self):
331
# We should only be getting asked for the serializer for
332
# RemoteRepositoryFormat objects except when the RemoteRepositoryFormat
333
# object is a concrete instance for a RemoteRepository. In this case
334
# we know the creating_repo and can use it to supply the serializer.
335
self._creating_repo._ensure_real()
336
return self._creating_repo._real_repository._format._serializer
339
class RemoteRepository(_RpcHelper):
340
"""Repository accessed over rpc.
342
For the moment most operations are performed using local transport-backed
346
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
347
"""Create a RemoteRepository instance.
349
:param remote_bzrdir: The bzrdir hosting this repository.
350
:param format: The RemoteFormat object to use.
351
:param real_repository: If not None, a local implementation of the
352
repository logic for the repository, usually accessing the data
354
:param _client: Private testing parameter - override the smart client
355
to be used by the repository.
358
self._real_repository = real_repository
360
self._real_repository = None
361
self.bzrdir = remote_bzrdir
363
self._client = remote_bzrdir._client
365
self._client = _client
366
self._format = format
367
self._lock_mode = None
368
self._lock_token = None
370
self._leave_lock = False
371
self._unstacked_provider = graph.CachingParentsProvider(
372
get_parent_map=self._get_parent_map_rpc)
373
self._unstacked_provider.disable_cache()
375
# These depend on the actual remote format, so force them off for
376
# maximum compatibility. XXX: In future these should depend on the
377
# remote repository instance, but this is irrelevant until we perform
378
# reconcile via an RPC call.
379
self._reconcile_does_inventory_gc = False
380
self._reconcile_fixes_text_parents = False
381
self._reconcile_backsup_inventory = False
382
self.base = self.bzrdir.transport.base
383
# Additional places to query for data.
384
self._fallback_repositories = []
387
return "%s(%s)" % (self.__class__.__name__, self.base)
391
def abort_write_group(self, suppress_errors=False):
392
"""Complete a write group on the decorated repository.
394
Smart methods peform operations in a single step so this api
395
is not really applicable except as a compatibility thunk
396
for older plugins that don't use e.g. the CommitBuilder
399
:param suppress_errors: see Repository.abort_write_group.
402
return self._real_repository.abort_write_group(
403
suppress_errors=suppress_errors)
405
def commit_write_group(self):
406
"""Complete a write group on the decorated repository.
408
Smart methods peform operations in a single step so this api
409
is not really applicable except as a compatibility thunk
410
for older plugins that don't use e.g. the CommitBuilder
414
return self._real_repository.commit_write_group()
416
def _ensure_real(self):
417
"""Ensure that there is a _real_repository set.
419
Used before calls to self._real_repository.
421
if self._real_repository is None:
422
self.bzrdir._ensure_real()
423
self._set_real_repository(
424
self.bzrdir._real_bzrdir.open_repository())
426
def _translate_error(self, err, **context):
427
self.bzrdir._translate_error(err, repository=self, **context)
429
def find_text_key_references(self):
430
"""Find the text key references within the repository.
432
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
433
revision_ids. Each altered file-ids has the exact revision_ids that
434
altered it listed explicitly.
435
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
436
to whether they were referred to by the inventory of the
437
revision_id that they contain. The inventory texts from all present
438
revision ids are assessed to generate this report.
441
return self._real_repository.find_text_key_references()
443
def _generate_text_key_index(self):
444
"""Generate a new text key index for the repository.
446
This is an expensive function that will take considerable time to run.
448
:return: A dict mapping (file_id, revision_id) tuples to a list of
449
parents, also (file_id, revision_id) tuples.
452
return self._real_repository._generate_text_key_index()
454
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
455
def get_revision_graph(self, revision_id=None):
456
"""See Repository.get_revision_graph()."""
457
return self._get_revision_graph(revision_id)
459
def _get_revision_graph(self, revision_id):
460
"""Private method for using with old (< 1.2) servers to fallback."""
461
if revision_id is None:
463
elif revision.is_null(revision_id):
466
path = self.bzrdir._path_for_remote_call(self._client)
467
response = self._call_expecting_body(
468
'Repository.get_revision_graph', path, revision_id)
469
response_tuple, response_handler = response
470
if response_tuple[0] != 'ok':
471
raise errors.UnexpectedSmartServerResponse(response_tuple)
472
coded = response_handler.read_body_bytes()
474
# no revisions in this repository!
476
lines = coded.split('\n')
479
d = tuple(line.split())
480
revision_graph[d[0]] = d[1:]
482
return revision_graph
485
"""See Repository._get_sink()."""
487
return self._real_repository._get_sink()
489
def has_revision(self, revision_id):
490
"""See Repository.has_revision()."""
491
if revision_id == NULL_REVISION:
492
# The null revision is always present.
494
path = self.bzrdir._path_for_remote_call(self._client)
495
response = self._call('Repository.has_revision', path, revision_id)
496
if response[0] not in ('yes', 'no'):
497
raise errors.UnexpectedSmartServerResponse(response)
498
if response[0] == 'yes':
500
for fallback_repo in self._fallback_repositories:
501
if fallback_repo.has_revision(revision_id):
505
def has_revisions(self, revision_ids):
506
"""See Repository.has_revisions()."""
507
# FIXME: This does many roundtrips, particularly when there are
508
# fallback repositories. -- mbp 20080905
510
for revision_id in revision_ids:
511
if self.has_revision(revision_id):
512
result.add(revision_id)
515
def has_same_location(self, other):
516
return (self.__class__ == other.__class__ and
517
self.bzrdir.transport.base == other.bzrdir.transport.base)
519
def get_graph(self, other_repository=None):
520
"""Return the graph for this repository format"""
521
parents_provider = self._make_parents_provider(other_repository)
522
return graph.Graph(parents_provider)
524
def gather_stats(self, revid=None, committers=None):
525
"""See Repository.gather_stats()."""
526
path = self.bzrdir._path_for_remote_call(self._client)
527
# revid can be None to indicate no revisions, not just NULL_REVISION
528
if revid is None or revision.is_null(revid):
532
if committers is None or not committers:
533
fmt_committers = 'no'
535
fmt_committers = 'yes'
536
response_tuple, response_handler = self._call_expecting_body(
537
'Repository.gather_stats', path, fmt_revid, fmt_committers)
538
if response_tuple[0] != 'ok':
539
raise errors.UnexpectedSmartServerResponse(response_tuple)
541
body = response_handler.read_body_bytes()
543
for line in body.split('\n'):
546
key, val_text = line.split(':')
547
if key in ('revisions', 'size', 'committers'):
548
result[key] = int(val_text)
549
elif key in ('firstrev', 'latestrev'):
550
values = val_text.split(' ')[1:]
551
result[key] = (float(values[0]), long(values[1]))
555
def find_branches(self, using=False):
556
"""See Repository.find_branches()."""
557
# should be an API call to the server.
559
return self._real_repository.find_branches(using=using)
561
def get_physical_lock_status(self):
562
"""See Repository.get_physical_lock_status()."""
563
# should be an API call to the server.
565
return self._real_repository.get_physical_lock_status()
567
def is_in_write_group(self):
568
"""Return True if there is an open write group.
570
write groups are only applicable locally for the smart server..
572
if self._real_repository:
573
return self._real_repository.is_in_write_group()
576
return self._lock_count >= 1
579
"""See Repository.is_shared()."""
580
path = self.bzrdir._path_for_remote_call(self._client)
581
response = self._call('Repository.is_shared', path)
582
if response[0] not in ('yes', 'no'):
583
raise SmartProtocolError('unexpected response code %s' % (response,))
584
return response[0] == 'yes'
586
def is_write_locked(self):
587
return self._lock_mode == 'w'
590
# wrong eventually - want a local lock cache context
591
if not self._lock_mode:
592
self._lock_mode = 'r'
594
self._unstacked_provider.enable_cache(cache_misses=False)
595
if self._real_repository is not None:
596
self._real_repository.lock_read()
598
self._lock_count += 1
600
def _remote_lock_write(self, token):
601
path = self.bzrdir._path_for_remote_call(self._client)
604
err_context = {'token': token}
605
response = self._call('Repository.lock_write', path, token,
607
if response[0] == 'ok':
611
raise errors.UnexpectedSmartServerResponse(response)
613
def lock_write(self, token=None, _skip_rpc=False):
614
if not self._lock_mode:
616
if self._lock_token is not None:
617
if token != self._lock_token:
618
raise errors.TokenMismatch(token, self._lock_token)
619
self._lock_token = token
621
self._lock_token = self._remote_lock_write(token)
622
# if self._lock_token is None, then this is something like packs or
623
# svn where we don't get to lock the repo, or a weave style repository
624
# where we cannot lock it over the wire and attempts to do so will
626
if self._real_repository is not None:
627
self._real_repository.lock_write(token=self._lock_token)
628
if token is not None:
629
self._leave_lock = True
631
self._leave_lock = False
632
self._lock_mode = 'w'
634
self._unstacked_provider.enable_cache(cache_misses=False)
635
elif self._lock_mode == 'r':
636
raise errors.ReadOnlyError(self)
638
self._lock_count += 1
639
return self._lock_token or None
641
def leave_lock_in_place(self):
642
if not self._lock_token:
643
raise NotImplementedError(self.leave_lock_in_place)
644
self._leave_lock = True
646
def dont_leave_lock_in_place(self):
647
if not self._lock_token:
648
raise NotImplementedError(self.dont_leave_lock_in_place)
649
self._leave_lock = False
651
def _set_real_repository(self, repository):
652
"""Set the _real_repository for this repository.
654
:param repository: The repository to fallback to for non-hpss
655
implemented operations.
657
if self._real_repository is not None:
658
raise AssertionError('_real_repository is already set')
659
if isinstance(repository, RemoteRepository):
660
raise AssertionError()
661
self._real_repository = repository
662
for fb in self._fallback_repositories:
663
self._real_repository.add_fallback_repository(fb)
664
if self._lock_mode == 'w':
665
# if we are already locked, the real repository must be able to
666
# acquire the lock with our token.
667
self._real_repository.lock_write(self._lock_token)
668
elif self._lock_mode == 'r':
669
self._real_repository.lock_read()
671
def start_write_group(self):
672
"""Start a write group on the decorated repository.
674
Smart methods peform operations in a single step so this api
675
is not really applicable except as a compatibility thunk
676
for older plugins that don't use e.g. the CommitBuilder
680
return self._real_repository.start_write_group()
682
def _unlock(self, token):
683
path = self.bzrdir._path_for_remote_call(self._client)
685
# with no token the remote repository is not persistently locked.
687
err_context = {'token': token}
688
response = self._call('Repository.unlock', path, token,
690
if response == ('ok',):
693
raise errors.UnexpectedSmartServerResponse(response)
696
self._lock_count -= 1
697
if self._lock_count > 0:
699
self._unstacked_provider.disable_cache()
700
old_mode = self._lock_mode
701
self._lock_mode = None
703
# The real repository is responsible at present for raising an
704
# exception if it's in an unfinished write group. However, it
705
# normally will *not* actually remove the lock from disk - that's
706
# done by the server on receiving the Repository.unlock call.
707
# This is just to let the _real_repository stay up to date.
708
if self._real_repository is not None:
709
self._real_repository.unlock()
711
# The rpc-level lock should be released even if there was a
712
# problem releasing the vfs-based lock.
714
# Only write-locked repositories need to make a remote method
715
# call to perfom the unlock.
716
old_token = self._lock_token
717
self._lock_token = None
718
if not self._leave_lock:
719
self._unlock(old_token)
721
def break_lock(self):
722
# should hand off to the network
724
return self._real_repository.break_lock()
726
def _get_tarball(self, compression):
727
"""Return a TemporaryFile containing a repository tarball.
729
Returns None if the server does not support sending tarballs.
732
path = self.bzrdir._path_for_remote_call(self._client)
734
response, protocol = self._call_expecting_body(
735
'Repository.tarball', path, compression)
736
except errors.UnknownSmartMethod:
737
protocol.cancel_read_body()
739
if response[0] == 'ok':
740
# Extract the tarball and return it
741
t = tempfile.NamedTemporaryFile()
742
# TODO: rpc layer should read directly into it...
743
t.write(protocol.read_body_bytes())
746
raise errors.UnexpectedSmartServerResponse(response)
748
def sprout(self, to_bzrdir, revision_id=None):
749
# TODO: Option to control what format is created?
751
dest_repo = self._real_repository._format.initialize(to_bzrdir,
753
dest_repo.fetch(self, revision_id=revision_id)
756
### These methods are just thin shims to the VFS object for now.
758
def revision_tree(self, revision_id):
760
return self._real_repository.revision_tree(revision_id)
762
def get_serializer_format(self):
764
return self._real_repository.get_serializer_format()
766
def get_commit_builder(self, branch, parents, config, timestamp=None,
767
timezone=None, committer=None, revprops=None,
769
# FIXME: It ought to be possible to call this without immediately
770
# triggering _ensure_real. For now it's the easiest thing to do.
772
real_repo = self._real_repository
773
builder = real_repo.get_commit_builder(branch, parents,
774
config, timestamp=timestamp, timezone=timezone,
775
committer=committer, revprops=revprops, revision_id=revision_id)
778
def add_fallback_repository(self, repository):
779
"""Add a repository to use for looking up data not held locally.
781
:param repository: A repository.
783
# XXX: At the moment the RemoteRepository will allow fallbacks
784
# unconditionally - however, a _real_repository will usually exist,
785
# and may raise an error if it's not accommodated by the underlying
786
# format. Eventually we should check when opening the repository
787
# whether it's willing to allow them or not.
789
# We need to accumulate additional repositories here, to pass them in
791
self._fallback_repositories.append(repository)
792
# They are also seen by the fallback repository. If it doesn't exist
793
# yet they'll be added then. This implicitly copies them.
796
def add_inventory(self, revid, inv, parents):
798
return self._real_repository.add_inventory(revid, inv, parents)
800
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
803
return self._real_repository.add_inventory_by_delta(basis_revision_id,
804
delta, new_revision_id, parents)
806
def add_revision(self, rev_id, rev, inv=None, config=None):
808
return self._real_repository.add_revision(
809
rev_id, rev, inv=inv, config=config)
812
def get_inventory(self, revision_id):
814
return self._real_repository.get_inventory(revision_id)
816
def iter_inventories(self, revision_ids):
818
return self._real_repository.iter_inventories(revision_ids)
821
def get_revision(self, revision_id):
823
return self._real_repository.get_revision(revision_id)
825
def get_transaction(self):
827
return self._real_repository.get_transaction()
830
def clone(self, a_bzrdir, revision_id=None):
832
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
834
def make_working_trees(self):
835
"""See Repository.make_working_trees"""
837
return self._real_repository.make_working_trees()
839
def revision_ids_to_search_result(self, result_set):
840
"""Convert a set of revision ids to a graph SearchResult."""
841
result_parents = set()
842
for parents in self.get_graph().get_parent_map(
843
result_set).itervalues():
844
result_parents.update(parents)
845
included_keys = result_set.intersection(result_parents)
846
start_keys = result_set.difference(included_keys)
847
exclude_keys = result_parents.difference(result_set)
848
result = graph.SearchResult(start_keys, exclude_keys,
849
len(result_set), result_set)
853
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
854
"""Return the revision ids that other has that this does not.
856
These are returned in topological order.
858
revision_id: only return revision ids included by revision_id.
860
return repository.InterRepository.get(
861
other, self).search_missing_revision_ids(revision_id, find_ghosts)
863
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
864
# Not delegated to _real_repository so that InterRepository.get has a
865
# chance to find an InterRepository specialised for RemoteRepository.
866
if self.has_same_location(source):
867
# check that last_revision is in 'from' and then return a
869
if (revision_id is not None and
870
not revision.is_null(revision_id)):
871
self.get_revision(revision_id)
873
inter = repository.InterRepository.get(source, self)
875
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
876
except NotImplementedError:
877
raise errors.IncompatibleRepositories(source, self)
879
def create_bundle(self, target, base, fileobj, format=None):
881
self._real_repository.create_bundle(target, base, fileobj, format)
884
def get_ancestry(self, revision_id, topo_sorted=True):
886
return self._real_repository.get_ancestry(revision_id, topo_sorted)
888
def fileids_altered_by_revision_ids(self, revision_ids):
890
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
892
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
894
return self._real_repository._get_versioned_file_checker(
895
revisions, revision_versions_cache)
897
def iter_files_bytes(self, desired_files):
898
"""See Repository.iter_file_bytes.
901
return self._real_repository.iter_files_bytes(desired_files)
904
def _fetch_order(self):
905
"""Decorate the real repository for now.
907
In the long term getting this back from the remote repository as part
908
of open would be more efficient.
911
return self._real_repository._fetch_order
914
def _fetch_uses_deltas(self):
915
"""Decorate the real repository for now.
917
In the long term getting this back from the remote repository as part
918
of open would be more efficient.
921
return self._real_repository._fetch_uses_deltas
924
def _fetch_reconcile(self):
925
"""Decorate the real repository for now.
927
In the long term getting this back from the remote repository as part
928
of open would be more efficient.
931
return self._real_repository._fetch_reconcile
933
def get_parent_map(self, revision_ids):
934
"""See bzrlib.Graph.get_parent_map()."""
935
return self._make_parents_provider().get_parent_map(revision_ids)
937
def _get_parent_map_rpc(self, keys):
938
"""Helper for get_parent_map that performs the RPC."""
939
medium = self._client._medium
940
if medium._is_remote_before((1, 2)):
941
# We already found out that the server can't understand
942
# Repository.get_parent_map requests, so just fetch the whole
944
# XXX: Note that this will issue a deprecation warning. This is ok
945
# :- its because we're working with a deprecated server anyway, and
946
# the user will almost certainly have seen a warning about the
947
# server version already.
948
rg = self.get_revision_graph()
949
# There is an api discrepency between get_parent_map and
950
# get_revision_graph. Specifically, a "key:()" pair in
951
# get_revision_graph just means a node has no parents. For
952
# "get_parent_map" it means the node is a ghost. So fix up the
953
# graph to correct this.
954
# https://bugs.launchpad.net/bzr/+bug/214894
955
# There is one other "bug" which is that ghosts in
956
# get_revision_graph() are not returned at all. But we won't worry
957
# about that for now.
958
for node_id, parent_ids in rg.iteritems():
960
rg[node_id] = (NULL_REVISION,)
961
rg[NULL_REVISION] = ()
966
raise ValueError('get_parent_map(None) is not valid')
967
if NULL_REVISION in keys:
968
keys.discard(NULL_REVISION)
969
found_parents = {NULL_REVISION:()}
974
# TODO(Needs analysis): We could assume that the keys being requested
975
# from get_parent_map are in a breadth first search, so typically they
976
# will all be depth N from some common parent, and we don't have to
977
# have the server iterate from the root parent, but rather from the
978
# keys we're searching; and just tell the server the keyspace we
979
# already have; but this may be more traffic again.
981
# Transform self._parents_map into a search request recipe.
982
# TODO: Manage this incrementally to avoid covering the same path
983
# repeatedly. (The server will have to on each request, but the less
984
# work done the better).
985
parents_map = self._unstacked_provider.get_cached_map()
986
if parents_map is None:
987
# Repository is not locked, so there's no cache.
989
start_set = set(parents_map)
990
result_parents = set()
991
for parents in parents_map.itervalues():
992
result_parents.update(parents)
993
stop_keys = result_parents.difference(start_set)
994
included_keys = start_set.intersection(result_parents)
995
start_set.difference_update(included_keys)
996
recipe = (start_set, stop_keys, len(parents_map))
997
body = self._serialise_search_recipe(recipe)
998
path = self.bzrdir._path_for_remote_call(self._client)
1000
if type(key) is not str:
1002
"key %r not a plain string" % (key,))
1003
verb = 'Repository.get_parent_map'
1004
args = (path,) + tuple(keys)
1006
response = self._call_with_body_bytes_expecting_body(
1008
except errors.UnknownSmartMethod:
1009
# Server does not support this method, so get the whole graph.
1010
# Worse, we have to force a disconnection, because the server now
1011
# doesn't realise it has a body on the wire to consume, so the
1012
# only way to recover is to abandon the connection.
1014
'Server is too old for fast get_parent_map, reconnecting. '
1015
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1017
# To avoid having to disconnect repeatedly, we keep track of the
1018
# fact the server doesn't understand remote methods added in 1.2.
1019
medium._remember_remote_is_before((1, 2))
1020
return self.get_revision_graph(None)
1021
response_tuple, response_handler = response
1022
if response_tuple[0] not in ['ok']:
1023
response_handler.cancel_read_body()
1024
raise errors.UnexpectedSmartServerResponse(response_tuple)
1025
if response_tuple[0] == 'ok':
1026
coded = bz2.decompress(response_handler.read_body_bytes())
1028
# no revisions found
1030
lines = coded.split('\n')
1033
d = tuple(line.split())
1035
revision_graph[d[0]] = d[1:]
1037
# No parents - so give the Graph result (NULL_REVISION,).
1038
revision_graph[d[0]] = (NULL_REVISION,)
1039
return revision_graph
1042
def get_signature_text(self, revision_id):
1044
return self._real_repository.get_signature_text(revision_id)
1047
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1048
def get_revision_graph_with_ghosts(self, revision_ids=None):
1050
return self._real_repository.get_revision_graph_with_ghosts(
1051
revision_ids=revision_ids)
1054
def get_inventory_xml(self, revision_id):
1056
return self._real_repository.get_inventory_xml(revision_id)
1058
def deserialise_inventory(self, revision_id, xml):
1060
return self._real_repository.deserialise_inventory(revision_id, xml)
1062
def reconcile(self, other=None, thorough=False):
1064
return self._real_repository.reconcile(other=other, thorough=thorough)
1066
def all_revision_ids(self):
1068
return self._real_repository.all_revision_ids()
1071
def get_deltas_for_revisions(self, revisions):
1073
return self._real_repository.get_deltas_for_revisions(revisions)
1076
def get_revision_delta(self, revision_id):
1078
return self._real_repository.get_revision_delta(revision_id)
1081
def revision_trees(self, revision_ids):
1083
return self._real_repository.revision_trees(revision_ids)
1086
def get_revision_reconcile(self, revision_id):
1088
return self._real_repository.get_revision_reconcile(revision_id)
1091
def check(self, revision_ids=None):
1093
return self._real_repository.check(revision_ids=revision_ids)
1095
def copy_content_into(self, destination, revision_id=None):
1097
return self._real_repository.copy_content_into(
1098
destination, revision_id=revision_id)
1100
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1101
# get a tarball of the remote repository, and copy from that into the
1103
from bzrlib import osutils
1105
# TODO: Maybe a progress bar while streaming the tarball?
1106
note("Copying repository content as tarball...")
1107
tar_file = self._get_tarball('bz2')
1108
if tar_file is None:
1110
destination = to_bzrdir.create_repository()
1112
tar = tarfile.open('repository', fileobj=tar_file,
1114
tmpdir = osutils.mkdtemp()
1116
_extract_tar(tar, tmpdir)
1117
tmp_bzrdir = BzrDir.open(tmpdir)
1118
tmp_repo = tmp_bzrdir.open_repository()
1119
tmp_repo.copy_content_into(destination, revision_id)
1121
osutils.rmtree(tmpdir)
1125
# TODO: Suggestion from john: using external tar is much faster than
1126
# python's tarfile library, but it may not work on windows.
1129
def inventories(self):
1130
"""Decorate the real repository for now.
1132
In the long term a full blown network facility is needed to
1133
avoid creating a real repository object locally.
1136
return self._real_repository.inventories
1140
"""Compress the data within the repository.
1142
This is not currently implemented within the smart server.
1145
return self._real_repository.pack()
1148
def revisions(self):
1149
"""Decorate the real repository for now.
1151
In the short term this should become a real object to intercept graph
1154
In the long term a full blown network facility is needed.
1157
return self._real_repository.revisions
1159
def set_make_working_trees(self, new_value):
1161
self._real_repository.set_make_working_trees(new_value)
1164
def signatures(self):
1165
"""Decorate the real repository for now.
1167
In the long term a full blown network facility is needed to avoid
1168
creating a real repository object locally.
1171
return self._real_repository.signatures
1174
def sign_revision(self, revision_id, gpg_strategy):
1176
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1180
"""Decorate the real repository for now.
1182
In the long term a full blown network facility is needed to avoid
1183
creating a real repository object locally.
1186
return self._real_repository.texts
1189
def get_revisions(self, revision_ids):
1191
return self._real_repository.get_revisions(revision_ids)
1193
def supports_rich_root(self):
1195
return self._real_repository.supports_rich_root()
1197
def iter_reverse_revision_history(self, revision_id):
1199
return self._real_repository.iter_reverse_revision_history(revision_id)
1202
def _serializer(self):
1204
return self._real_repository._serializer
1206
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1208
return self._real_repository.store_revision_signature(
1209
gpg_strategy, plaintext, revision_id)
1211
def add_signature_text(self, revision_id, signature):
1213
return self._real_repository.add_signature_text(revision_id, signature)
1215
def has_signature_for_revision_id(self, revision_id):
1217
return self._real_repository.has_signature_for_revision_id(revision_id)
1219
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1221
return self._real_repository.item_keys_introduced_by(revision_ids,
1222
_files_pb=_files_pb)
1224
def revision_graph_can_have_wrong_parents(self):
1225
# The answer depends on the remote repo format.
1227
return self._real_repository.revision_graph_can_have_wrong_parents()
1229
def _find_inconsistent_revision_parents(self):
1231
return self._real_repository._find_inconsistent_revision_parents()
1233
def _check_for_inconsistent_revision_parents(self):
1235
return self._real_repository._check_for_inconsistent_revision_parents()
1237
def _make_parents_provider(self, other=None):
1238
providers = [self._unstacked_provider]
1239
if other is not None:
1240
providers.insert(0, other)
1241
providers.extend(r._make_parents_provider() for r in
1242
self._fallback_repositories)
1243
return graph._StackedParentsProvider(providers)
1245
def _serialise_search_recipe(self, recipe):
1246
"""Serialise a graph search recipe.
1248
:param recipe: A search recipe (start, stop, count).
1249
:return: Serialised bytes.
1251
start_keys = ' '.join(recipe[0])
1252
stop_keys = ' '.join(recipe[1])
1253
count = str(recipe[2])
1254
return '\n'.join((start_keys, stop_keys, count))
1257
path = self.bzrdir._path_for_remote_call(self._client)
1259
response = self._call('PackRepository.autopack', path)
1260
except errors.UnknownSmartMethod:
1262
self._real_repository._pack_collection.autopack()
1264
if self._real_repository is not None:
1265
# Reset the real repository's cache of pack names.
1266
# XXX: At some point we may be able to skip this and just rely on
1267
# the automatic retry logic to do the right thing, but for now we
1268
# err on the side of being correct rather than being optimal.
1269
self._real_repository._pack_collection.reload_pack_names()
1270
if response[0] != 'ok':
1271
raise errors.UnexpectedSmartServerResponse(response)
1274
def _length_prefix(bytes):
1275
return struct.pack('!L', len(bytes))
1278
class RemoteBranchLockableFiles(LockableFiles):
1279
"""A 'LockableFiles' implementation that talks to a smart server.
1281
This is not a public interface class.
1284
def __init__(self, bzrdir, _client):
1285
self.bzrdir = bzrdir
1286
self._client = _client
1287
self._need_find_modes = True
1288
LockableFiles.__init__(
1289
self, bzrdir.get_branch_transport(None),
1290
'lock', lockdir.LockDir)
1292
def _find_modes(self):
1293
# RemoteBranches don't let the client set the mode of control files.
1294
self._dir_mode = None
1295
self._file_mode = None
1298
class RemoteBranchFormat(branch.BranchFormat):
1301
super(RemoteBranchFormat, self).__init__()
1302
self._matchingbzrdir = RemoteBzrDirFormat()
1303
self._matchingbzrdir.set_branch_format(self)
1305
def __eq__(self, other):
1306
return (isinstance(other, RemoteBranchFormat) and
1307
self.__dict__ == other.__dict__)
1309
def get_format_description(self):
1310
return 'Remote BZR Branch'
1312
def get_format_string(self):
1313
return 'Remote BZR Branch'
1315
def open(self, a_bzrdir):
1316
return a_bzrdir.open_branch()
1318
def initialize(self, a_bzrdir):
1319
# Delegate to a _real object here - the RemoteBzrDir format now
1320
# supports delegating to parameterised branch formats and as such
1321
# this RemoteBranchFormat method is only called when no specific format
1323
if not isinstance(a_bzrdir, RemoteBzrDir):
1324
result = a_bzrdir.create_branch()
1326
a_bzrdir._ensure_real()
1327
result = a_bzrdir._real_bzrdir.create_branch()
1328
if not isinstance(result, RemoteBranch):
1329
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1332
def supports_tags(self):
1333
# Remote branches might support tags, but we won't know until we
1334
# access the real remote branch.
1338
class RemoteBranch(branch.Branch, _RpcHelper):
1339
"""Branch stored on a server accessed by HPSS RPC.
1341
At the moment most operations are mapped down to simple file operations.
1344
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1346
"""Create a RemoteBranch instance.
1348
:param real_branch: An optional local implementation of the branch
1349
format, usually accessing the data via the VFS.
1350
:param _client: Private parameter for testing.
1352
# We intentionally don't call the parent class's __init__, because it
1353
# will try to assign to self.tags, which is a property in this subclass.
1354
# And the parent's __init__ doesn't do much anyway.
1355
self._revision_id_to_revno_cache = None
1356
self._partial_revision_id_to_revno_cache = {}
1357
self._revision_history_cache = None
1358
self._last_revision_info_cache = None
1359
self._merge_sorted_revisions_cache = None
1360
self.bzrdir = remote_bzrdir
1361
if _client is not None:
1362
self._client = _client
1364
self._client = remote_bzrdir._client
1365
self.repository = remote_repository
1366
if real_branch is not None:
1367
self._real_branch = real_branch
1368
# Give the remote repository the matching real repo.
1369
real_repo = self._real_branch.repository
1370
if isinstance(real_repo, RemoteRepository):
1371
real_repo._ensure_real()
1372
real_repo = real_repo._real_repository
1373
self.repository._set_real_repository(real_repo)
1374
# Give the branch the remote repository to let fast-pathing happen.
1375
self._real_branch.repository = self.repository
1377
self._real_branch = None
1378
# Fill out expected attributes of branch for bzrlib api users.
1379
self._format = RemoteBranchFormat()
1380
self.base = self.bzrdir.root_transport.base
1381
self._control_files = None
1382
self._lock_mode = None
1383
self._lock_token = None
1384
self._repo_lock_token = None
1385
self._lock_count = 0
1386
self._leave_lock = False
1387
# The base class init is not called, so we duplicate this:
1388
hooks = branch.Branch.hooks['open']
1391
self._setup_stacking()
1393
def _setup_stacking(self):
1394
# configure stacking into the remote repository, by reading it from
1397
fallback_url = self.get_stacked_on_url()
1398
except (errors.NotStacked, errors.UnstackableBranchFormat,
1399
errors.UnstackableRepositoryFormat), e:
1401
# it's relative to this branch...
1402
fallback_url = urlutils.join(self.base, fallback_url)
1403
transports = [self.bzrdir.root_transport]
1404
if self._real_branch is not None:
1405
transports.append(self._real_branch._transport)
1406
stacked_on = branch.Branch.open(fallback_url,
1407
possible_transports=transports)
1408
self.repository.add_fallback_repository(stacked_on.repository)
1410
def _get_real_transport(self):
1411
# if we try vfs access, return the real branch's vfs transport
1413
return self._real_branch._transport
1415
_transport = property(_get_real_transport)
1418
return "%s(%s)" % (self.__class__.__name__, self.base)
1422
def _ensure_real(self):
1423
"""Ensure that there is a _real_branch set.
1425
Used before calls to self._real_branch.
1427
if self._real_branch is None:
1428
if not vfs.vfs_enabled():
1429
raise AssertionError('smart server vfs must be enabled '
1430
'to use vfs implementation')
1431
self.bzrdir._ensure_real()
1432
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1433
if self.repository._real_repository is None:
1434
# Give the remote repository the matching real repo.
1435
real_repo = self._real_branch.repository
1436
if isinstance(real_repo, RemoteRepository):
1437
real_repo._ensure_real()
1438
real_repo = real_repo._real_repository
1439
self.repository._set_real_repository(real_repo)
1440
# Give the real branch the remote repository to let fast-pathing
1442
self._real_branch.repository = self.repository
1443
if self._lock_mode == 'r':
1444
self._real_branch.lock_read()
1445
elif self._lock_mode == 'w':
1446
self._real_branch.lock_write(token=self._lock_token)
1448
def _translate_error(self, err, **context):
1449
self.repository._translate_error(err, branch=self, **context)
1451
def _clear_cached_state(self):
1452
super(RemoteBranch, self)._clear_cached_state()
1453
if self._real_branch is not None:
1454
self._real_branch._clear_cached_state()
1456
def _clear_cached_state_of_remote_branch_only(self):
1457
"""Like _clear_cached_state, but doesn't clear the cache of
1460
This is useful when falling back to calling a method of
1461
self._real_branch that changes state. In that case the underlying
1462
branch changes, so we need to invalidate this RemoteBranch's cache of
1463
it. However, there's no need to invalidate the _real_branch's cache
1464
too, in fact doing so might harm performance.
1466
super(RemoteBranch, self)._clear_cached_state()
1469
def control_files(self):
1470
# Defer actually creating RemoteBranchLockableFiles until its needed,
1471
# because it triggers an _ensure_real that we otherwise might not need.
1472
if self._control_files is None:
1473
self._control_files = RemoteBranchLockableFiles(
1474
self.bzrdir, self._client)
1475
return self._control_files
1477
def _get_checkout_format(self):
1479
return self._real_branch._get_checkout_format()
1481
def get_physical_lock_status(self):
1482
"""See Branch.get_physical_lock_status()."""
1483
# should be an API call to the server, as branches must be lockable.
1485
return self._real_branch.get_physical_lock_status()
1487
def get_stacked_on_url(self):
1488
"""Get the URL this branch is stacked against.
1490
:raises NotStacked: If the branch is not stacked.
1491
:raises UnstackableBranchFormat: If the branch does not support
1493
:raises UnstackableRepositoryFormat: If the repository does not support
1497
# there may not be a repository yet, so we can't use
1498
# self._translate_error, so we can't use self._call either.
1499
response = self._client.call('Branch.get_stacked_on_url',
1500
self._remote_path())
1501
except errors.ErrorFromSmartServer, err:
1502
# there may not be a repository yet, so we can't call through
1503
# its _translate_error
1504
_translate_error(err, branch=self)
1505
except errors.UnknownSmartMethod, err:
1507
return self._real_branch.get_stacked_on_url()
1508
if response[0] != 'ok':
1509
raise errors.UnexpectedSmartServerResponse(response)
1512
def lock_read(self):
1513
self.repository.lock_read()
1514
if not self._lock_mode:
1515
self._lock_mode = 'r'
1516
self._lock_count = 1
1517
if self._real_branch is not None:
1518
self._real_branch.lock_read()
1520
self._lock_count += 1
1522
def _remote_lock_write(self, token):
1524
branch_token = repo_token = ''
1526
branch_token = token
1527
repo_token = self.repository.lock_write()
1528
self.repository.unlock()
1529
err_context = {'token': token}
1530
response = self._call(
1531
'Branch.lock_write', self._remote_path(), branch_token,
1532
repo_token or '', **err_context)
1533
if response[0] != 'ok':
1534
raise errors.UnexpectedSmartServerResponse(response)
1535
ok, branch_token, repo_token = response
1536
return branch_token, repo_token
1538
def lock_write(self, token=None):
1539
if not self._lock_mode:
1540
# Lock the branch and repo in one remote call.
1541
remote_tokens = self._remote_lock_write(token)
1542
self._lock_token, self._repo_lock_token = remote_tokens
1543
if not self._lock_token:
1544
raise SmartProtocolError('Remote server did not return a token!')
1545
# Tell the self.repository object that it is locked.
1546
self.repository.lock_write(
1547
self._repo_lock_token, _skip_rpc=True)
1549
if self._real_branch is not None:
1550
self._real_branch.lock_write(token=self._lock_token)
1551
if token is not None:
1552
self._leave_lock = True
1554
self._leave_lock = False
1555
self._lock_mode = 'w'
1556
self._lock_count = 1
1557
elif self._lock_mode == 'r':
1558
raise errors.ReadOnlyTransaction
1560
if token is not None:
1561
# A token was given to lock_write, and we're relocking, so
1562
# check that the given token actually matches the one we
1564
if token != self._lock_token:
1565
raise errors.TokenMismatch(token, self._lock_token)
1566
self._lock_count += 1
1567
# Re-lock the repository too.
1568
self.repository.lock_write(self._repo_lock_token)
1569
return self._lock_token or None
1571
def _unlock(self, branch_token, repo_token):
1572
err_context = {'token': str((branch_token, repo_token))}
1573
response = self._call(
1574
'Branch.unlock', self._remote_path(), branch_token,
1575
repo_token or '', **err_context)
1576
if response == ('ok',):
1578
raise errors.UnexpectedSmartServerResponse(response)
1582
self._lock_count -= 1
1583
if not self._lock_count:
1584
self._clear_cached_state()
1585
mode = self._lock_mode
1586
self._lock_mode = None
1587
if self._real_branch is not None:
1588
if (not self._leave_lock and mode == 'w' and
1589
self._repo_lock_token):
1590
# If this RemoteBranch will remove the physical lock
1591
# for the repository, make sure the _real_branch
1592
# doesn't do it first. (Because the _real_branch's
1593
# repository is set to be the RemoteRepository.)
1594
self._real_branch.repository.leave_lock_in_place()
1595
self._real_branch.unlock()
1597
# Only write-locked branched need to make a remote method
1598
# call to perfom the unlock.
1600
if not self._lock_token:
1601
raise AssertionError('Locked, but no token!')
1602
branch_token = self._lock_token
1603
repo_token = self._repo_lock_token
1604
self._lock_token = None
1605
self._repo_lock_token = None
1606
if not self._leave_lock:
1607
self._unlock(branch_token, repo_token)
1609
self.repository.unlock()
1611
def break_lock(self):
1613
return self._real_branch.break_lock()
1615
def leave_lock_in_place(self):
1616
if not self._lock_token:
1617
raise NotImplementedError(self.leave_lock_in_place)
1618
self._leave_lock = True
1620
def dont_leave_lock_in_place(self):
1621
if not self._lock_token:
1622
raise NotImplementedError(self.dont_leave_lock_in_place)
1623
self._leave_lock = False
1625
def _last_revision_info(self):
1626
response = self._call('Branch.last_revision_info', self._remote_path())
1627
if response[0] != 'ok':
1628
raise SmartProtocolError('unexpected response code %s' % (response,))
1629
revno = int(response[1])
1630
last_revision = response[2]
1631
return (revno, last_revision)
1633
def _gen_revision_history(self):
1634
"""See Branch._gen_revision_history()."""
1635
response_tuple, response_handler = self._call_expecting_body(
1636
'Branch.revision_history', self._remote_path())
1637
if response_tuple[0] != 'ok':
1638
raise errors.UnexpectedSmartServerResponse(response_tuple)
1639
result = response_handler.read_body_bytes().split('\x00')
1644
def _remote_path(self):
1645
return self.bzrdir._path_for_remote_call(self._client)
1647
def _set_last_revision_descendant(self, revision_id, other_branch,
1648
allow_diverged=False, allow_overwrite_descendant=False):
1649
# This performs additional work to meet the hook contract; while its
1650
# undesirable, we have to synthesise the revno to call the hook, and
1651
# not calling the hook is worse as it means changes can't be prevented.
1652
# Having calculated this though, we can't just call into
1653
# set_last_revision_info as a simple call, because there is a set_rh
1654
# hook that some folk may still be using.
1655
old_revno, old_revid = self.last_revision_info()
1656
history = self._lefthand_history(revision_id)
1657
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1658
err_context = {'other_branch': other_branch}
1659
response = self._call('Branch.set_last_revision_ex',
1660
self._remote_path(), self._lock_token, self._repo_lock_token,
1661
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1663
self._clear_cached_state()
1664
if len(response) != 3 and response[0] != 'ok':
1665
raise errors.UnexpectedSmartServerResponse(response)
1666
new_revno, new_revision_id = response[1:]
1667
self._last_revision_info_cache = new_revno, new_revision_id
1668
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1669
if self._real_branch is not None:
1670
cache = new_revno, new_revision_id
1671
self._real_branch._last_revision_info_cache = cache
1673
def _set_last_revision(self, revision_id):
1674
old_revno, old_revid = self.last_revision_info()
1675
# This performs additional work to meet the hook contract; while its
1676
# undesirable, we have to synthesise the revno to call the hook, and
1677
# not calling the hook is worse as it means changes can't be prevented.
1678
# Having calculated this though, we can't just call into
1679
# set_last_revision_info as a simple call, because there is a set_rh
1680
# hook that some folk may still be using.
1681
history = self._lefthand_history(revision_id)
1682
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1683
self._clear_cached_state()
1684
response = self._call('Branch.set_last_revision',
1685
self._remote_path(), self._lock_token, self._repo_lock_token,
1687
if response != ('ok',):
1688
raise errors.UnexpectedSmartServerResponse(response)
1689
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1692
def set_revision_history(self, rev_history):
1693
# Send just the tip revision of the history; the server will generate
1694
# the full history from that. If the revision doesn't exist in this
1695
# branch, NoSuchRevision will be raised.
1696
if rev_history == []:
1699
rev_id = rev_history[-1]
1700
self._set_last_revision(rev_id)
1701
for hook in branch.Branch.hooks['set_rh']:
1702
hook(self, rev_history)
1703
self._cache_revision_history(rev_history)
1705
def get_parent(self):
1707
return self._real_branch.get_parent()
1709
def _get_parent_location(self):
1710
# Used by tests, when checking normalisation of given vs stored paths.
1712
return self._real_branch._get_parent_location()
1714
def set_parent(self, url):
1716
return self._real_branch.set_parent(url)
1718
def _set_parent_location(self, url):
1719
# Used by tests, to poke bad urls into branch configurations
1721
self.set_parent(url)
1724
return self._real_branch._set_parent_location(url)
1726
def set_stacked_on_url(self, stacked_location):
1727
"""Set the URL this branch is stacked against.
1729
:raises UnstackableBranchFormat: If the branch does not support
1731
:raises UnstackableRepositoryFormat: If the repository does not support
1735
return self._real_branch.set_stacked_on_url(stacked_location)
1737
def sprout(self, to_bzrdir, revision_id=None):
1738
branch_format = to_bzrdir._format._branch_format
1739
if (branch_format is None or
1740
isinstance(branch_format, RemoteBranchFormat)):
1741
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1742
# implies the same thing), but RemoteBranches can't be created at
1743
# arbitrary URLs. So create a branch in the same format as
1744
# _real_branch instead.
1745
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1746
# to_bzrdir.create_branch to create a RemoteBranch after all...
1748
result = self._real_branch._format.initialize(to_bzrdir)
1749
self.copy_content_into(result, revision_id=revision_id)
1750
result.set_parent(self.bzrdir.root_transport.base)
1752
result = branch.Branch.sprout(
1753
self, to_bzrdir, revision_id=revision_id)
1757
def pull(self, source, overwrite=False, stop_revision=None,
1759
self._clear_cached_state_of_remote_branch_only()
1761
return self._real_branch.pull(
1762
source, overwrite=overwrite, stop_revision=stop_revision,
1763
_override_hook_target=self, **kwargs)
1766
def push(self, target, overwrite=False, stop_revision=None):
1768
return self._real_branch.push(
1769
target, overwrite=overwrite, stop_revision=stop_revision,
1770
_override_hook_source_branch=self)
1772
def is_locked(self):
1773
return self._lock_count >= 1
1776
def revision_id_to_revno(self, revision_id):
1778
return self._real_branch.revision_id_to_revno(revision_id)
1781
def set_last_revision_info(self, revno, revision_id):
1782
# XXX: These should be returned by the set_last_revision_info verb
1783
old_revno, old_revid = self.last_revision_info()
1784
self._run_pre_change_branch_tip_hooks(revno, revision_id)
1785
revision_id = ensure_null(revision_id)
1787
response = self._call('Branch.set_last_revision_info',
1788
self._remote_path(), self._lock_token, self._repo_lock_token,
1789
str(revno), revision_id)
1790
except errors.UnknownSmartMethod:
1792
self._clear_cached_state_of_remote_branch_only()
1793
self._real_branch.set_last_revision_info(revno, revision_id)
1794
self._last_revision_info_cache = revno, revision_id
1796
if response == ('ok',):
1797
self._clear_cached_state()
1798
self._last_revision_info_cache = revno, revision_id
1799
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1800
# Update the _real_branch's cache too.
1801
if self._real_branch is not None:
1802
cache = self._last_revision_info_cache
1803
self._real_branch._last_revision_info_cache = cache
1805
raise errors.UnexpectedSmartServerResponse(response)
1808
def generate_revision_history(self, revision_id, last_rev=None,
1810
medium = self._client._medium
1811
if not medium._is_remote_before((1, 6)):
1812
# Use a smart method for 1.6 and above servers
1814
self._set_last_revision_descendant(revision_id, other_branch,
1815
allow_diverged=True, allow_overwrite_descendant=True)
1817
except errors.UnknownSmartMethod:
1818
medium._remember_remote_is_before((1, 6))
1819
self._clear_cached_state_of_remote_branch_only()
1820
self.set_revision_history(self._lefthand_history(revision_id,
1821
last_rev=last_rev,other_branch=other_branch))
1826
return self._real_branch.tags
1828
def set_push_location(self, location):
1830
return self._real_branch.set_push_location(location)
1833
def update_revisions(self, other, stop_revision=None, overwrite=False,
1835
"""See Branch.update_revisions."""
1838
if stop_revision is None:
1839
stop_revision = other.last_revision()
1840
if revision.is_null(stop_revision):
1841
# if there are no commits, we're done.
1843
self.fetch(other, stop_revision)
1846
# Just unconditionally set the new revision. We don't care if
1847
# the branches have diverged.
1848
self._set_last_revision(stop_revision)
1850
medium = self._client._medium
1851
if not medium._is_remote_before((1, 6)):
1853
self._set_last_revision_descendant(stop_revision, other)
1855
except errors.UnknownSmartMethod:
1856
medium._remember_remote_is_before((1, 6))
1857
# Fallback for pre-1.6 servers: check for divergence
1858
# client-side, then do _set_last_revision.
1859
last_rev = revision.ensure_null(self.last_revision())
1861
graph = self.repository.get_graph()
1862
if self._check_if_descendant_or_diverged(
1863
stop_revision, last_rev, graph, other):
1864
# stop_revision is a descendant of last_rev, but we aren't
1865
# overwriting, so we're done.
1867
self._set_last_revision(stop_revision)
1872
def _extract_tar(tar, to_dir):
1873
"""Extract all the contents of a tarfile object.
1875
A replacement for extractall, which is not present in python2.4
1878
tar.extract(tarinfo, to_dir)
1881
def _translate_error(err, **context):
1882
"""Translate an ErrorFromSmartServer into a more useful error.
1884
Possible context keys:
1892
If the error from the server doesn't match a known pattern, then
1893
UnknownErrorFromSmartServer is raised.
1897
return context[name]
1898
except KeyError, key_err:
1899
mutter('Missing key %r in context %r', key_err.args[0], context)
1902
"""Get the path from the context if present, otherwise use first error
1906
return context['path']
1907
except KeyError, key_err:
1909
return err.error_args[0]
1910
except IndexError, idx_err:
1912
'Missing key %r in context %r', key_err.args[0], context)
1915
if err.error_verb == 'NoSuchRevision':
1916
raise NoSuchRevision(find('branch'), err.error_args[0])
1917
elif err.error_verb == 'nosuchrevision':
1918
raise NoSuchRevision(find('repository'), err.error_args[0])
1919
elif err.error_tuple == ('nobranch',):
1920
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1921
elif err.error_verb == 'norepository':
1922
raise errors.NoRepositoryPresent(find('bzrdir'))
1923
elif err.error_verb == 'LockContention':
1924
raise errors.LockContention('(remote lock)')
1925
elif err.error_verb == 'UnlockableTransport':
1926
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1927
elif err.error_verb == 'LockFailed':
1928
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1929
elif err.error_verb == 'TokenMismatch':
1930
raise errors.TokenMismatch(find('token'), '(remote token)')
1931
elif err.error_verb == 'Diverged':
1932
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1933
elif err.error_verb == 'TipChangeRejected':
1934
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1935
elif err.error_verb == 'UnstackableBranchFormat':
1936
raise errors.UnstackableBranchFormat(*err.error_args)
1937
elif err.error_verb == 'UnstackableRepositoryFormat':
1938
raise errors.UnstackableRepositoryFormat(*err.error_args)
1939
elif err.error_verb == 'NotStacked':
1940
raise errors.NotStacked(branch=find('branch'))
1941
elif err.error_verb == 'PermissionDenied':
1943
if len(err.error_args) >= 2:
1944
extra = err.error_args[1]
1947
raise errors.PermissionDenied(path, extra=extra)
1948
elif err.error_verb == 'ReadError':
1950
raise errors.ReadError(path)
1951
elif err.error_verb == 'NoSuchFile':
1953
raise errors.NoSuchFile(path)
1954
elif err.error_verb == 'FileExists':
1955
raise errors.FileExists(err.error_args[0])
1956
elif err.error_verb == 'DirectoryNotEmpty':
1957
raise errors.DirectoryNotEmpty(err.error_args[0])
1958
elif err.error_verb == 'ShortReadvError':
1959
args = err.error_args
1960
raise errors.ShortReadvError(
1961
args[0], int(args[1]), int(args[2]), int(args[3]))
1962
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1963
encoding = str(err.error_args[0]) # encoding must always be a string
1964
val = err.error_args[1]
1965
start = int(err.error_args[2])
1966
end = int(err.error_args[3])
1967
reason = str(err.error_args[4]) # reason must always be a string
1968
if val.startswith('u:'):
1969
val = val[2:].decode('utf-8')
1970
elif val.startswith('s:'):
1971
val = val[2:].decode('base64')
1972
if err.error_verb == 'UnicodeDecodeError':
1973
raise UnicodeDecodeError(encoding, val, start, end, reason)
1974
elif err.error_verb == 'UnicodeEncodeError':
1975
raise UnicodeEncodeError(encoding, val, start, end, reason)
1976
elif err.error_verb == 'ReadOnlyError':
1977
raise errors.TransportNotPossible('readonly transport')
1978
raise errors.UnknownErrorFromSmartServer(err)