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
45
from bzrlib.util import bencode
46
from bzrlib.versionedfile import VersionedFiles
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)
72
# Note: RemoteBzrDirFormat is in bzrdir.py
74
class RemoteBzrDir(BzrDir, _RpcHelper):
75
"""Control directory on a remote server, accessed via bzr:// or similar."""
77
def __init__(self, transport, _client=None):
78
"""Construct a RemoteBzrDir.
80
:param _client: Private parameter for testing. Disables probing and the
83
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
84
# this object holds a delegated bzrdir that uses file-level operations
85
# to talk to the other side
86
self._real_bzrdir = None
89
medium = transport.get_smart_medium()
90
self._client = client._SmartClient(medium)
92
self._client = _client
95
path = self._path_for_remote_call(self._client)
96
response = self._call('BzrDir.open', path)
97
if response not in [('yes',), ('no',)]:
98
raise errors.UnexpectedSmartServerResponse(response)
99
if response == ('no',):
100
raise errors.NotBranchError(path=transport.base)
102
def _ensure_real(self):
103
"""Ensure that there is a _real_bzrdir set.
105
Used before calls to self._real_bzrdir.
107
if not self._real_bzrdir:
108
self._real_bzrdir = BzrDir.open_from_transport(
109
self.root_transport, _server_formats=False)
111
def _translate_error(self, err, **context):
112
_translate_error(err, bzrdir=self, **context)
114
def cloning_metadir(self, stacked=False):
116
return self._real_bzrdir.cloning_metadir(stacked)
118
def create_repository(self, shared=False):
120
self._real_bzrdir.create_repository(shared=shared)
121
return self.open_repository()
123
def destroy_repository(self):
124
"""See BzrDir.destroy_repository"""
126
self._real_bzrdir.destroy_repository()
128
def create_branch(self):
130
real_branch = self._real_bzrdir.create_branch()
131
return RemoteBranch(self, self.find_repository(), real_branch)
133
def destroy_branch(self):
134
"""See BzrDir.destroy_branch"""
136
self._real_bzrdir.destroy_branch()
138
def create_workingtree(self, revision_id=None, from_branch=None):
139
raise errors.NotLocalUrl(self.transport.base)
141
def find_branch_format(self):
142
"""Find the branch 'format' for this bzrdir.
144
This might be a synthetic object for e.g. RemoteBranch and SVN.
146
b = self.open_branch()
149
def get_branch_reference(self):
150
"""See BzrDir.get_branch_reference()."""
151
path = self._path_for_remote_call(self._client)
152
response = self._call('BzrDir.open_branch', path)
153
if response[0] == 'ok':
154
if response[1] == '':
155
# branch at this location.
158
# a branch reference, use the existing BranchReference logic.
161
raise errors.UnexpectedSmartServerResponse(response)
163
def _get_tree_branch(self):
164
"""See BzrDir._get_tree_branch()."""
165
return None, self.open_branch()
167
def open_branch(self, _unsupported=False):
169
raise NotImplementedError('unsupported flag support not implemented yet.')
170
reference_url = self.get_branch_reference()
171
if reference_url is None:
172
# branch at this location.
173
return RemoteBranch(self, self.find_repository())
175
# a branch reference, use the existing BranchReference logic.
176
format = BranchReferenceFormat()
177
return format.open(self, _found=True, location=reference_url)
179
def open_repository(self):
180
path = self._path_for_remote_call(self._client)
181
verb = 'BzrDir.find_repositoryV2'
183
response = self._call(verb, path)
184
except errors.UnknownSmartMethod:
185
verb = 'BzrDir.find_repository'
186
response = self._call(verb, path)
187
if response[0] != 'ok':
188
raise errors.UnexpectedSmartServerResponse(response)
189
if verb == 'BzrDir.find_repository':
190
# servers that don't support the V2 method don't support external
192
response = response + ('no', )
193
if not (len(response) == 5):
194
raise SmartProtocolError('incorrect response length %s' % (response,))
195
if response[1] == '':
196
format = RemoteRepositoryFormat()
197
format.rich_root_data = (response[2] == 'yes')
198
format.supports_tree_reference = (response[3] == 'yes')
199
# No wire format to check this yet.
200
format.supports_external_lookups = (response[4] == 'yes')
201
# Used to support creating a real format instance when needed.
202
format._creating_bzrdir = self
203
return RemoteRepository(self, format)
205
raise errors.NoRepositoryPresent(self)
207
def open_workingtree(self, recommend_upgrade=True):
209
if self._real_bzrdir.has_workingtree():
210
raise errors.NotLocalUrl(self.root_transport)
212
raise errors.NoWorkingTree(self.root_transport.base)
214
def _path_for_remote_call(self, client):
215
"""Return the path to be used for this bzrdir in a remote call."""
216
return client.remote_path_from_transport(self.root_transport)
218
def get_branch_transport(self, branch_format):
220
return self._real_bzrdir.get_branch_transport(branch_format)
222
def get_repository_transport(self, repository_format):
224
return self._real_bzrdir.get_repository_transport(repository_format)
226
def get_workingtree_transport(self, workingtree_format):
228
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
230
def can_convert_format(self):
231
"""Upgrading of remote bzrdirs is not supported yet."""
234
def needs_format_conversion(self, format=None):
235
"""Upgrading of remote bzrdirs is not supported yet."""
237
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
238
% 'needs_format_conversion(format=None)')
241
def clone(self, url, revision_id=None, force_new_repo=False,
242
preserve_stacking=False):
244
return self._real_bzrdir.clone(url, revision_id=revision_id,
245
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
247
def get_config(self):
249
return self._real_bzrdir.get_config()
252
class RemoteRepositoryFormat(repository.RepositoryFormat):
253
"""Format for repositories accessed over a _SmartClient.
255
Instances of this repository are represented by RemoteRepository
258
The RemoteRepositoryFormat is parameterized during construction
259
to reflect the capabilities of the real, remote format. Specifically
260
the attributes rich_root_data and supports_tree_reference are set
261
on a per instance basis, and are not set (and should not be) at
265
_matchingbzrdir = RemoteBzrDirFormat()
267
def initialize(self, a_bzrdir, shared=False):
268
if not isinstance(a_bzrdir, RemoteBzrDir):
269
prior_repo = self._creating_bzrdir.open_repository()
270
prior_repo._ensure_real()
271
return prior_repo._real_repository._format.initialize(
272
a_bzrdir, shared=shared)
273
return a_bzrdir.create_repository(shared=shared)
275
def open(self, a_bzrdir):
276
if not isinstance(a_bzrdir, RemoteBzrDir):
277
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
278
return a_bzrdir.open_repository()
280
def get_format_description(self):
281
return 'bzr remote repository'
283
def __eq__(self, other):
284
return self.__class__ == other.__class__
286
def check_conversion_target(self, target_format):
287
if self.rich_root_data and not target_format.rich_root_data:
288
raise errors.BadConversionTarget(
289
'Does not support rich root data.', target_format)
290
if (self.supports_tree_reference and
291
not getattr(target_format, 'supports_tree_reference', False)):
292
raise errors.BadConversionTarget(
293
'Does not support nested trees', target_format)
296
class RemoteRepository(_RpcHelper):
297
"""Repository accessed over rpc.
299
For the moment most operations are performed using local transport-backed
303
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
304
"""Create a RemoteRepository instance.
306
:param remote_bzrdir: The bzrdir hosting this repository.
307
:param format: The RemoteFormat object to use.
308
:param real_repository: If not None, a local implementation of the
309
repository logic for the repository, usually accessing the data
311
:param _client: Private testing parameter - override the smart client
312
to be used by the repository.
315
self._real_repository = real_repository
317
self._real_repository = None
318
self.bzrdir = remote_bzrdir
320
self._client = remote_bzrdir._client
322
self._client = _client
323
self._format = format
324
self._lock_mode = None
325
self._lock_token = None
327
self._leave_lock = False
328
self._unstacked_provider = graph.CachingParentsProvider(
329
get_parent_map=self._get_parent_map_rpc)
330
self._unstacked_provider.disable_cache()
332
# These depend on the actual remote format, so force them off for
333
# maximum compatibility. XXX: In future these should depend on the
334
# remote repository instance, but this is irrelevant until we perform
335
# reconcile via an RPC call.
336
self._reconcile_does_inventory_gc = False
337
self._reconcile_fixes_text_parents = False
338
self._reconcile_backsup_inventory = False
339
self.base = self.bzrdir.transport.base
340
# Additional places to query for data.
341
self._fallback_repositories = []
342
self.texts = RemoteVersionedFiles(self, 'texts')
343
self.inventories = RemoteVersionedFiles(self, 'inventories')
344
self.signatures = RemoteVersionedFiles(self, 'signatures')
345
self.revisions = RemoteVersionedFiles(self, 'revisions')
347
self.texts, self.inventories, self.signatures, self.revisions]
350
return "%s(%s)" % (self.__class__.__name__, self.base)
354
def abort_write_group(self, suppress_errors=False):
355
"""Complete a write group on the decorated repository.
357
Smart methods peform operations in a single step so this api
358
is not really applicable except as a compatibility thunk
359
for older plugins that don't use e.g. the CommitBuilder
362
:param suppress_errors: see Repository.abort_write_group.
365
return self._real_repository.abort_write_group(
366
suppress_errors=suppress_errors)
368
def commit_write_group(self):
369
"""Complete a write group on the decorated repository.
371
Smart methods peform operations in a single step so this api
372
is not really applicable except as a compatibility thunk
373
for older plugins that don't use e.g. the CommitBuilder
377
return self._real_repository.commit_write_group()
379
def _ensure_real(self):
380
"""Ensure that there is a _real_repository set.
382
Used before calls to self._real_repository.
384
if self._real_repository is None:
385
self.bzrdir._ensure_real()
386
self._set_real_repository(
387
self.bzrdir._real_bzrdir.open_repository())
389
def _translate_error(self, err, **context):
390
self.bzrdir._translate_error(err, repository=self, **context)
392
def find_text_key_references(self):
393
"""Find the text key references within the repository.
395
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
396
revision_ids. Each altered file-ids has the exact revision_ids that
397
altered it listed explicitly.
398
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
399
to whether they were referred to by the inventory of the
400
revision_id that they contain. The inventory texts from all present
401
revision ids are assessed to generate this report.
404
return self._real_repository.find_text_key_references()
406
def _generate_text_key_index(self):
407
"""Generate a new text key index for the repository.
409
This is an expensive function that will take considerable time to run.
411
:return: A dict mapping (file_id, revision_id) tuples to a list of
412
parents, also (file_id, revision_id) tuples.
415
return self._real_repository._generate_text_key_index()
417
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
418
def get_revision_graph(self, revision_id=None):
419
"""See Repository.get_revision_graph()."""
420
return self._get_revision_graph(revision_id)
422
def _get_revision_graph(self, revision_id):
423
"""Private method for using with old (< 1.2) servers to fallback."""
424
if revision_id is None:
426
elif revision.is_null(revision_id):
429
path = self.bzrdir._path_for_remote_call(self._client)
430
response = self._call_expecting_body(
431
'Repository.get_revision_graph', path, revision_id)
432
response_tuple, response_handler = response
433
if response_tuple[0] != 'ok':
434
raise errors.UnexpectedSmartServerResponse(response_tuple)
435
coded = response_handler.read_body_bytes()
437
# no revisions in this repository!
439
lines = coded.split('\n')
442
d = tuple(line.split())
443
revision_graph[d[0]] = d[1:]
445
return revision_graph
447
def has_revision(self, revision_id):
448
"""See Repository.has_revision()."""
449
if revision_id == NULL_REVISION:
450
# The null revision is always present.
452
path = self.bzrdir._path_for_remote_call(self._client)
453
response = self._call('Repository.has_revision', path, revision_id)
454
if response[0] not in ('yes', 'no'):
455
raise errors.UnexpectedSmartServerResponse(response)
456
if response[0] == 'yes':
458
for fallback_repo in self._fallback_repositories:
459
if fallback_repo.has_revision(revision_id):
463
def has_revisions(self, revision_ids):
464
"""See Repository.has_revisions()."""
465
# FIXME: This does many roundtrips, particularly when there are
466
# fallback repositories. -- mbp 20080905
468
for revision_id in revision_ids:
469
if self.has_revision(revision_id):
470
result.add(revision_id)
473
def has_same_location(self, other):
474
return (self.__class__ == other.__class__ and
475
self.bzrdir.transport.base == other.bzrdir.transport.base)
477
def get_graph(self, other_repository=None):
478
"""Return the graph for this repository format"""
479
parents_provider = self._make_parents_provider(other_repository)
480
return graph.Graph(parents_provider)
482
def gather_stats(self, revid=None, committers=None):
483
"""See Repository.gather_stats()."""
484
path = self.bzrdir._path_for_remote_call(self._client)
485
# revid can be None to indicate no revisions, not just NULL_REVISION
486
if revid is None or revision.is_null(revid):
490
if committers is None or not committers:
491
fmt_committers = 'no'
493
fmt_committers = 'yes'
494
response_tuple, response_handler = self._call_expecting_body(
495
'Repository.gather_stats', path, fmt_revid, fmt_committers)
496
if response_tuple[0] != 'ok':
497
raise errors.UnexpectedSmartServerResponse(response_tuple)
499
body = response_handler.read_body_bytes()
501
for line in body.split('\n'):
504
key, val_text = line.split(':')
505
if key in ('revisions', 'size', 'committers'):
506
result[key] = int(val_text)
507
elif key in ('firstrev', 'latestrev'):
508
values = val_text.split(' ')[1:]
509
result[key] = (float(values[0]), long(values[1]))
513
def find_branches(self, using=False):
514
"""See Repository.find_branches()."""
515
# should be an API call to the server.
517
return self._real_repository.find_branches(using=using)
519
def get_physical_lock_status(self):
520
"""See Repository.get_physical_lock_status()."""
521
# should be an API call to the server.
523
return self._real_repository.get_physical_lock_status()
525
def is_in_write_group(self):
526
"""Return True if there is an open write group.
528
write groups are only applicable locally for the smart server..
530
if self._real_repository:
531
return self._real_repository.is_in_write_group()
534
return self._lock_count >= 1
537
"""See Repository.is_shared()."""
538
path = self.bzrdir._path_for_remote_call(self._client)
539
response = self._call('Repository.is_shared', path)
540
if response[0] not in ('yes', 'no'):
541
raise SmartProtocolError('unexpected response code %s' % (response,))
542
return response[0] == 'yes'
544
def is_write_locked(self):
545
return self._lock_mode == 'w'
548
# wrong eventually - want a local lock cache context
549
if not self._lock_mode:
550
self._lock_mode = 'r'
552
self._unstacked_provider.enable_cache(cache_misses=False)
553
if self._real_repository is not None:
554
self._real_repository.lock_read()
556
self._lock_count += 1
558
def _remote_lock_write(self, token):
559
path = self.bzrdir._path_for_remote_call(self._client)
562
err_context = {'token': token}
563
response = self._call('Repository.lock_write', path, token,
565
if response[0] == 'ok':
569
raise errors.UnexpectedSmartServerResponse(response)
571
def lock_write(self, token=None, _skip_rpc=False):
572
if not self._lock_mode:
574
if self._lock_token is not None:
575
if token != self._lock_token:
576
raise errors.TokenMismatch(token, self._lock_token)
577
self._lock_token = token
579
self._lock_token = self._remote_lock_write(token)
580
# if self._lock_token is None, then this is something like packs or
581
# svn where we don't get to lock the repo, or a weave style repository
582
# where we cannot lock it over the wire and attempts to do so will
584
if self._real_repository is not None:
585
self._real_repository.lock_write(token=self._lock_token)
586
if token is not None:
587
self._leave_lock = True
589
self._leave_lock = False
590
self._lock_mode = 'w'
592
self._unstacked_provider.enable_cache(cache_misses=False)
593
elif self._lock_mode == 'r':
594
raise errors.ReadOnlyError(self)
596
self._lock_count += 1
597
return self._lock_token or None
599
def leave_lock_in_place(self):
600
if not self._lock_token:
601
raise NotImplementedError(self.leave_lock_in_place)
602
self._leave_lock = True
604
def dont_leave_lock_in_place(self):
605
if not self._lock_token:
606
raise NotImplementedError(self.dont_leave_lock_in_place)
607
self._leave_lock = False
609
def _set_real_repository(self, repository):
610
"""Set the _real_repository for this repository.
612
:param repository: The repository to fallback to for non-hpss
613
implemented operations.
615
if self._real_repository is not None:
616
raise AssertionError('_real_repository is already set')
617
if isinstance(repository, RemoteRepository):
618
raise AssertionError()
619
self._real_repository = repository
620
for fb in self._fallback_repositories:
621
self._real_repository.add_fallback_repository(fb)
622
if self._lock_mode == 'w':
623
# if we are already locked, the real repository must be able to
624
# acquire the lock with our token.
625
self._real_repository.lock_write(self._lock_token)
626
elif self._lock_mode == 'r':
627
self._real_repository.lock_read()
629
def start_write_group(self):
630
"""Start a write group on the decorated repository.
632
Smart methods peform operations in a single step so this api
633
is not really applicable except as a compatibility thunk
634
for older plugins that don't use e.g. the CommitBuilder
638
return self._real_repository.start_write_group()
640
def _unlock(self, token):
641
path = self.bzrdir._path_for_remote_call(self._client)
643
# with no token the remote repository is not persistently locked.
645
err_context = {'token': token}
646
response = self._call('Repository.unlock', path, token,
648
if response == ('ok',):
651
raise errors.UnexpectedSmartServerResponse(response)
654
self._lock_count -= 1
655
if self._lock_count > 0:
657
self._unstacked_provider.disable_cache()
658
old_mode = self._lock_mode
659
self._lock_mode = None
661
# The real repository is responsible at present for raising an
662
# exception if it's in an unfinished write group. However, it
663
# normally will *not* actually remove the lock from disk - that's
664
# done by the server on receiving the Repository.unlock call.
665
# This is just to let the _real_repository stay up to date.
666
if self._real_repository is not None:
667
self._real_repository.unlock()
669
# The rpc-level lock should be released even if there was a
670
# problem releasing the vfs-based lock.
672
# Only write-locked repositories need to make a remote method
673
# call to perfom the unlock.
674
old_token = self._lock_token
675
self._lock_token = None
676
if not self._leave_lock:
677
self._unlock(old_token)
679
def break_lock(self):
680
# should hand off to the network
682
return self._real_repository.break_lock()
684
def _get_tarball(self, compression):
685
"""Return a TemporaryFile containing a repository tarball.
687
Returns None if the server does not support sending tarballs.
690
path = self.bzrdir._path_for_remote_call(self._client)
692
response, protocol = self._call_expecting_body(
693
'Repository.tarball', path, compression)
694
except errors.UnknownSmartMethod:
695
protocol.cancel_read_body()
697
if response[0] == 'ok':
698
# Extract the tarball and return it
699
t = tempfile.NamedTemporaryFile()
700
# TODO: rpc layer should read directly into it...
701
t.write(protocol.read_body_bytes())
704
raise errors.UnexpectedSmartServerResponse(response)
706
def sprout(self, to_bzrdir, revision_id=None):
707
# TODO: Option to control what format is created?
709
dest_repo = self._real_repository._format.initialize(to_bzrdir,
711
dest_repo.fetch(self, revision_id=revision_id)
714
### These methods are just thin shims to the VFS object for now.
716
def revision_tree(self, revision_id):
718
return self._real_repository.revision_tree(revision_id)
720
def get_serializer_format(self):
722
return self._real_repository.get_serializer_format()
724
def get_commit_builder(self, branch, parents, config, timestamp=None,
725
timezone=None, committer=None, revprops=None,
727
# FIXME: It ought to be possible to call this without immediately
728
# triggering _ensure_real. For now it's the easiest thing to do.
730
real_repo = self._real_repository
731
builder = real_repo.get_commit_builder(branch, parents,
732
config, timestamp=timestamp, timezone=timezone,
733
committer=committer, revprops=revprops, revision_id=revision_id)
736
def add_fallback_repository(self, repository):
737
"""Add a repository to use for looking up data not held locally.
739
:param repository: A repository.
741
# XXX: At the moment the RemoteRepository will allow fallbacks
742
# unconditionally - however, a _real_repository will usually exist,
743
# and may raise an error if it's not accommodated by the underlying
744
# format. Eventually we should check when opening the repository
745
# whether it's willing to allow them or not.
747
# We need to accumulate additional repositories here, to pass them in
749
self._fallback_repositories.append(repository)
750
# They are also seen by the fallback repository. If it doesn't exist
751
# yet they'll be added then. This implicitly copies them.
754
def add_inventory(self, revid, inv, parents):
756
return self._real_repository.add_inventory(revid, inv, parents)
758
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
761
return self._real_repository.add_inventory_by_delta(basis_revision_id,
762
delta, new_revision_id, parents)
764
def add_revision(self, rev_id, rev, inv=None, config=None):
766
return self._real_repository.add_revision(
767
rev_id, rev, inv=inv, config=config)
770
def get_inventory(self, revision_id):
772
return self._real_repository.get_inventory(revision_id)
774
def iter_inventories(self, revision_ids):
776
return self._real_repository.iter_inventories(revision_ids)
779
def get_revision(self, revision_id):
781
return self._real_repository.get_revision(revision_id)
783
def get_transaction(self):
785
return self._real_repository.get_transaction()
788
def clone(self, a_bzrdir, revision_id=None):
790
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
792
def make_working_trees(self):
793
"""See Repository.make_working_trees"""
795
return self._real_repository.make_working_trees()
797
def revision_ids_to_search_result(self, result_set):
798
"""Convert a set of revision ids to a graph SearchResult."""
799
result_parents = set()
800
for parents in self.get_graph().get_parent_map(
801
result_set).itervalues():
802
result_parents.update(parents)
803
included_keys = result_set.intersection(result_parents)
804
start_keys = result_set.difference(included_keys)
805
exclude_keys = result_parents.difference(result_set)
806
result = graph.SearchResult(start_keys, exclude_keys,
807
len(result_set), result_set)
811
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
812
"""Return the revision ids that other has that this does not.
814
These are returned in topological order.
816
revision_id: only return revision ids included by revision_id.
818
return repository.InterRepository.get(
819
other, self).search_missing_revision_ids(revision_id, find_ghosts)
821
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
822
# Not delegated to _real_repository so that InterRepository.get has a
823
# chance to find an InterRepository specialised for RemoteRepository.
824
if self.has_same_location(source):
825
# check that last_revision is in 'from' and then return a
827
if (revision_id is not None and
828
not revision.is_null(revision_id)):
829
self.get_revision(revision_id)
831
inter = repository.InterRepository.get(source, self)
833
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
834
except NotImplementedError:
835
raise errors.IncompatibleRepositories(source, self)
837
def create_bundle(self, target, base, fileobj, format=None):
839
self._real_repository.create_bundle(target, base, fileobj, format)
842
def get_ancestry(self, revision_id, topo_sorted=True):
844
return self._real_repository.get_ancestry(revision_id, topo_sorted)
846
def fileids_altered_by_revision_ids(self, revision_ids):
848
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
850
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
852
return self._real_repository._get_versioned_file_checker(
853
revisions, revision_versions_cache)
855
def iter_files_bytes(self, desired_files):
856
"""See Repository.iter_file_bytes.
859
return self._real_repository.iter_files_bytes(desired_files)
862
def _fetch_order(self):
863
"""Decorate the real repository for now.
865
In the long term getting this back from the remote repository as part
866
of open would be more efficient.
869
# self._ensure_real()
870
# return self._real_repository._fetch_order
873
def _fetch_uses_deltas(self):
874
"""Decorate the real repository for now.
876
In the long term getting this back from the remote repository as part
877
of open would be more efficient.
880
return self._real_repository._fetch_uses_deltas
883
def _fetch_reconcile(self):
884
"""Decorate the real repository for now.
886
In the long term getting this back from the remote repository as part
887
of open would be more efficient.
890
return self._real_repository._fetch_reconcile
892
def get_parent_map(self, revision_ids):
893
"""See bzrlib.Graph.get_parent_map()."""
894
return self._make_parents_provider().get_parent_map(revision_ids)
896
def _get_parent_map_rpc(self, keys):
897
"""Helper for get_parent_map that performs the RPC."""
898
medium = self._client._medium
899
if medium._is_remote_before((1, 2)):
900
# We already found out that the server can't understand
901
# Repository.get_parent_map requests, so just fetch the whole
903
# XXX: Note that this will issue a deprecation warning. This is ok
904
# :- its because we're working with a deprecated server anyway, and
905
# the user will almost certainly have seen a warning about the
906
# server version already.
907
rg = self.get_revision_graph()
908
# There is an api discrepency between get_parent_map and
909
# get_revision_graph. Specifically, a "key:()" pair in
910
# get_revision_graph just means a node has no parents. For
911
# "get_parent_map" it means the node is a ghost. So fix up the
912
# graph to correct this.
913
# https://bugs.launchpad.net/bzr/+bug/214894
914
# There is one other "bug" which is that ghosts in
915
# get_revision_graph() are not returned at all. But we won't worry
916
# about that for now.
917
for node_id, parent_ids in rg.iteritems():
919
rg[node_id] = (NULL_REVISION,)
920
rg[NULL_REVISION] = ()
925
raise ValueError('get_parent_map(None) is not valid')
926
if NULL_REVISION in keys:
927
keys.discard(NULL_REVISION)
928
found_parents = {NULL_REVISION:()}
933
# TODO(Needs analysis): We could assume that the keys being requested
934
# from get_parent_map are in a breadth first search, so typically they
935
# will all be depth N from some common parent, and we don't have to
936
# have the server iterate from the root parent, but rather from the
937
# keys we're searching; and just tell the server the keyspace we
938
# already have; but this may be more traffic again.
940
# Transform self._parents_map into a search request recipe.
941
# TODO: Manage this incrementally to avoid covering the same path
942
# repeatedly. (The server will have to on each request, but the less
943
# work done the better).
944
parents_map = self._unstacked_provider.get_cached_map()
945
if parents_map is None:
946
# Repository is not locked, so there's no cache.
948
start_set = set(parents_map)
949
result_parents = set()
950
for parents in parents_map.itervalues():
951
result_parents.update(parents)
952
stop_keys = result_parents.difference(start_set)
953
included_keys = start_set.intersection(result_parents)
954
start_set.difference_update(included_keys)
955
recipe = (start_set, stop_keys, len(parents_map))
956
path = self.bzrdir._path_for_remote_call(self._client)
958
if type(key) is not str:
960
"key %r not a plain string" % (key,))
961
verb = 'Repository.get_parent_map'
962
args = (path,) + tuple(keys)
964
response = self._call_with_body_bytes_expecting_body(
965
verb, args, _serialise_search_recipe(recipe))
966
except errors.UnknownSmartMethod:
967
# Server does not support this method, so get the whole graph.
968
# Worse, we have to force a disconnection, because the server now
969
# doesn't realise it has a body on the wire to consume, so the
970
# only way to recover is to abandon the connection.
972
'Server is too old for fast get_parent_map, reconnecting. '
973
'(Upgrade the server to Bazaar 1.2 to avoid this)')
975
# To avoid having to disconnect repeatedly, we keep track of the
976
# fact the server doesn't understand remote methods added in 1.2.
977
medium._remember_remote_is_before((1, 2))
978
return self.get_revision_graph(None)
979
response_tuple, response_handler = response
980
if response_tuple[0] not in ['ok']:
981
response_handler.cancel_read_body()
982
raise errors.UnexpectedSmartServerResponse(response_tuple)
983
if response_tuple[0] == 'ok':
984
coded = bz2.decompress(response_handler.read_body_bytes())
988
lines = coded.split('\n')
991
d = tuple(line.split())
993
revision_graph[d[0]] = d[1:]
995
# No parents - so give the Graph result (NULL_REVISION,).
996
revision_graph[d[0]] = (NULL_REVISION,)
997
return revision_graph
1000
def get_signature_text(self, revision_id):
1002
return self._real_repository.get_signature_text(revision_id)
1005
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1006
def get_revision_graph_with_ghosts(self, revision_ids=None):
1008
return self._real_repository.get_revision_graph_with_ghosts(
1009
revision_ids=revision_ids)
1012
def get_inventory_xml(self, revision_id):
1014
return self._real_repository.get_inventory_xml(revision_id)
1016
def deserialise_inventory(self, revision_id, xml):
1018
return self._real_repository.deserialise_inventory(revision_id, xml)
1020
def reconcile(self, other=None, thorough=False):
1022
return self._real_repository.reconcile(other=other, thorough=thorough)
1024
def all_revision_ids(self):
1026
return self._real_repository.all_revision_ids()
1029
def get_deltas_for_revisions(self, revisions):
1031
return self._real_repository.get_deltas_for_revisions(revisions)
1034
def get_revision_delta(self, revision_id):
1036
return self._real_repository.get_revision_delta(revision_id)
1039
def revision_trees(self, revision_ids):
1041
return self._real_repository.revision_trees(revision_ids)
1044
def get_revision_reconcile(self, revision_id):
1046
return self._real_repository.get_revision_reconcile(revision_id)
1049
def check(self, revision_ids=None):
1051
return self._real_repository.check(revision_ids=revision_ids)
1053
def copy_content_into(self, destination, revision_id=None):
1055
return self._real_repository.copy_content_into(
1056
destination, revision_id=revision_id)
1058
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1059
# get a tarball of the remote repository, and copy from that into the
1061
from bzrlib import osutils
1063
# TODO: Maybe a progress bar while streaming the tarball?
1064
note("Copying repository content as tarball...")
1065
tar_file = self._get_tarball('bz2')
1066
if tar_file is None:
1068
destination = to_bzrdir.create_repository()
1070
tar = tarfile.open('repository', fileobj=tar_file,
1072
tmpdir = osutils.mkdtemp()
1074
_extract_tar(tar, tmpdir)
1075
tmp_bzrdir = BzrDir.open(tmpdir)
1076
tmp_repo = tmp_bzrdir.open_repository()
1077
tmp_repo.copy_content_into(destination, revision_id)
1079
osutils.rmtree(tmpdir)
1083
# TODO: Suggestion from john: using external tar is much faster than
1084
# python's tarfile library, but it may not work on windows.
1088
"""Compress the data within the repository.
1090
This is not currently implemented within the smart server.
1093
return self._real_repository.pack()
1095
def set_make_working_trees(self, new_value):
1097
self._real_repository.set_make_working_trees(new_value)
1100
def sign_revision(self, revision_id, gpg_strategy):
1102
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1105
def get_revisions(self, revision_ids):
1107
return self._real_repository.get_revisions(revision_ids)
1109
def supports_rich_root(self):
1111
return self._real_repository.supports_rich_root()
1113
def iter_reverse_revision_history(self, revision_id):
1115
return self._real_repository.iter_reverse_revision_history(revision_id)
1118
def _serializer(self):
1120
return self._real_repository._serializer
1122
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1124
return self._real_repository.store_revision_signature(
1125
gpg_strategy, plaintext, revision_id)
1127
def add_signature_text(self, revision_id, signature):
1129
return self._real_repository.add_signature_text(revision_id, signature)
1131
def has_signature_for_revision_id(self, revision_id):
1133
return self._real_repository.has_signature_for_revision_id(revision_id)
1135
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1137
return self._real_repository.item_keys_introduced_by(revision_ids,
1138
_files_pb=_files_pb)
1140
def revision_graph_can_have_wrong_parents(self):
1141
# The answer depends on the remote repo format.
1143
return self._real_repository.revision_graph_can_have_wrong_parents()
1145
def _find_inconsistent_revision_parents(self):
1147
return self._real_repository._find_inconsistent_revision_parents()
1149
def _check_for_inconsistent_revision_parents(self):
1151
return self._real_repository._check_for_inconsistent_revision_parents()
1153
def _make_parents_provider(self, other=None):
1154
providers = [self._unstacked_provider]
1155
if other is not None:
1156
providers.insert(0, other)
1157
providers.extend(r._make_parents_provider() for r in
1158
self._fallback_repositories)
1159
return graph._StackedParentsProvider(providers)
1162
path = self.bzrdir._path_for_remote_call(self._client)
1164
response = self._call('PackRepository.autopack', path)
1165
except errors.UnknownSmartMethod:
1167
self._real_repository._pack_collection.autopack()
1169
if self._real_repository is not None:
1170
# Reset the real repository's cache of pack names.
1171
# XXX: At some point we may be able to skip this and just rely on
1172
# the automatic retry logic to do the right thing, but for now we
1173
# err on the side of being correct rather than being optimal.
1174
self._real_repository._pack_collection.reload_pack_names()
1175
if response[0] != 'ok':
1176
raise errors.UnexpectedSmartServerResponse(response)
1179
def _serialise_search_recipe(recipe):
1180
"""Serialise a graph search recipe.
1182
:param recipe: A search recipe (start, stop, count).
1183
:return: Serialised bytes.
1185
start_keys = ' '.join(recipe[0])
1186
stop_keys = ' '.join(recipe[1])
1187
count = str(recipe[2])
1188
return '\n'.join((start_keys, stop_keys, count))
1191
def _serialise_record_stream(stream):
1192
"""Takes a record stream as returned by get_record_stream and yields bytes.
1194
# Yields bencode of (sha1, storage_kind, key, parents, build_details,
1197
# - if sha1 is None, sha1 is ''
1198
# - if parents is None, parents is 'nil' (to distinguish it from empty
1200
# - if record has no _build_details, build_details is ()
1201
for record in stream:
1205
parents = record.parents
1206
if record.parents is None:
1208
if record.storage_kind.startswith('knit-'):
1209
build_details = record._build_details
1212
struct = (sha1, record.storage_kind, record.key, parents,
1213
build_details, record.get_bytes_as(record.storage_kind))
1214
yield bencode.bencode(struct)
1217
class RemoteVersionedFiles(VersionedFiles):
1219
def __init__(self, remote_repo, vf_name):
1220
self.remote_repo = remote_repo
1221
self.vf_name = vf_name
1223
def _get_real_vf(self):
1224
self.remote_repo._ensure_real()
1225
return getattr(self.remote_repo._real_repository, self.vf_name)
1227
def add_lines(self, version_id, parents, lines, parent_texts=None,
1228
left_matching_blocks=None, nostore_sha=None, random_id=False,
1229
check_content=True):
1230
real_vf = self._get_real_vf()
1231
return real_vf.add_lines(version_id, parents, lines,
1232
parent_texts=parent_texts,
1233
left_matching_blocks=left_matching_blocks,
1234
nostore_sha=nostore_sha, random_id=random_id,
1235
check_content=check_content)
1237
def add_mpdiffs(self, records):
1238
real_vf = self._get_real_vf()
1239
return real_vf.add_mpdiffs(records)
1241
def annotate(self, key):
1242
real_vf = self._get_real_vf()
1243
return real_vf.annotate(key)
1245
def check(self, progress_bar=None):
1246
real_vf = self._get_real_vf()
1247
return real_vf.check(progress_bar=progress_bar)
1249
def get_parent_map(self, keys):
1250
real_vf = self._get_real_vf()
1251
return real_vf.get_parent_map(keys)
1253
def get_record_stream(self, keys, ordering, include_delta_closure):
1254
real_vf = self._get_real_vf()
1255
return real_vf.get_record_stream(keys, ordering, include_delta_closure)
1257
def get_sha1s(self, keys):
1258
real_vf = self._get_real_vf()
1259
return real_vf.get_sha1s(keys)
1261
def insert_record_stream(self, stream, _record_serialiser=None):
1262
lock_token = self.remote_repo._lock_token
1263
if lock_token is None:
1265
if _record_serialiser is None:
1266
_record_serialiser = _serialise_record_stream
1267
# Tee the stream, because we may need to replay it if we have to
1268
# fallback to the VFS implementation. This unfortunately means
1269
# the entire record stream will temporarily be buffered in memory, even
1270
# if we don't need to fallback.
1271
# TODO: remember if this server accepts the insert_record_stream RPC,
1272
# and if so skip the buffering. (And if not, fallback immediately,
1273
# again no buffering.)
1274
stream, fallback_stream = itertools.tee(stream)
1275
byte_stream = _record_serialiser(stream)
1276
client = self.remote_repo._client
1277
path = self.remote_repo.bzrdir._path_for_remote_call(client)
1279
response = client.call_with_body_stream(
1280
('VersionedFile.insert_record_stream', path, self.vf_name,
1281
lock_token), byte_stream)
1282
except errors.UnknownSmartMethod:
1283
real_vf = self._get_real_vf()
1284
return real_vf.insert_record_stream(fallback_stream)
1286
response_tuple, response_handler = response
1287
if response_tuple != ('ok',):
1288
raise errors.UnexpectedSmartServerResponse(response_tuple)
1290
def iter_lines_added_or_present_in_keys(self, keys, pb=None):
1291
real_vf = self._get_real_vf()
1292
return real_vf.iter_lines_added_or_present_in_keys(keys, pb=pb)
1295
real_vf = self._get_real_vf()
1296
return real_vf.keys()
1298
def make_mpdiffs(self, keys):
1299
real_vf = self._get_real_vf()
1300
return real_vf.make_mpdiffs(keys)
1303
class RemoteBranchLockableFiles(LockableFiles):
1304
"""A 'LockableFiles' implementation that talks to a smart server.
1306
This is not a public interface class.
1309
def __init__(self, bzrdir, _client):
1310
self.bzrdir = bzrdir
1311
self._client = _client
1312
self._need_find_modes = True
1313
LockableFiles.__init__(
1314
self, bzrdir.get_branch_transport(None),
1315
'lock', lockdir.LockDir)
1317
def _find_modes(self):
1318
# RemoteBranches don't let the client set the mode of control files.
1319
self._dir_mode = None
1320
self._file_mode = None
1323
class RemoteBranchFormat(branch.BranchFormat):
1326
super(RemoteBranchFormat, self).__init__()
1327
self._matchingbzrdir = RemoteBzrDirFormat()
1328
self._matchingbzrdir.set_branch_format(self)
1330
def __eq__(self, other):
1331
return (isinstance(other, RemoteBranchFormat) and
1332
self.__dict__ == other.__dict__)
1334
def get_format_description(self):
1335
return 'Remote BZR Branch'
1337
def get_format_string(self):
1338
return 'Remote BZR Branch'
1340
def open(self, a_bzrdir):
1341
return a_bzrdir.open_branch()
1343
def initialize(self, a_bzrdir):
1344
return a_bzrdir.create_branch()
1346
def supports_tags(self):
1347
# Remote branches might support tags, but we won't know until we
1348
# access the real remote branch.
1352
class RemoteBranch(branch.Branch, _RpcHelper):
1353
"""Branch stored on a server accessed by HPSS RPC.
1355
At the moment most operations are mapped down to simple file operations.
1358
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1360
"""Create a RemoteBranch instance.
1362
:param real_branch: An optional local implementation of the branch
1363
format, usually accessing the data via the VFS.
1364
:param _client: Private parameter for testing.
1366
# We intentionally don't call the parent class's __init__, because it
1367
# will try to assign to self.tags, which is a property in this subclass.
1368
# And the parent's __init__ doesn't do much anyway.
1369
self._revision_id_to_revno_cache = None
1370
self._partial_revision_id_to_revno_cache = {}
1371
self._revision_history_cache = None
1372
self._last_revision_info_cache = None
1373
self._merge_sorted_revisions_cache = None
1374
self.bzrdir = remote_bzrdir
1375
if _client is not None:
1376
self._client = _client
1378
self._client = remote_bzrdir._client
1379
self.repository = remote_repository
1380
if real_branch is not None:
1381
self._real_branch = real_branch
1382
# Give the remote repository the matching real repo.
1383
real_repo = self._real_branch.repository
1384
if isinstance(real_repo, RemoteRepository):
1385
real_repo._ensure_real()
1386
real_repo = real_repo._real_repository
1387
self.repository._set_real_repository(real_repo)
1388
# Give the branch the remote repository to let fast-pathing happen.
1389
self._real_branch.repository = self.repository
1391
self._real_branch = None
1392
# Fill out expected attributes of branch for bzrlib api users.
1393
self._format = RemoteBranchFormat()
1394
self.base = self.bzrdir.root_transport.base
1395
self._control_files = None
1396
self._lock_mode = None
1397
self._lock_token = None
1398
self._repo_lock_token = None
1399
self._lock_count = 0
1400
self._leave_lock = False
1401
# The base class init is not called, so we duplicate this:
1402
hooks = branch.Branch.hooks['open']
1405
self._setup_stacking()
1407
def _setup_stacking(self):
1408
# configure stacking into the remote repository, by reading it from
1411
fallback_url = self.get_stacked_on_url()
1412
except (errors.NotStacked, errors.UnstackableBranchFormat,
1413
errors.UnstackableRepositoryFormat), e:
1415
# it's relative to this branch...
1416
fallback_url = urlutils.join(self.base, fallback_url)
1417
transports = [self.bzrdir.root_transport]
1418
if self._real_branch is not None:
1419
transports.append(self._real_branch._transport)
1420
stacked_on = branch.Branch.open(fallback_url,
1421
possible_transports=transports)
1422
self.repository.add_fallback_repository(stacked_on.repository)
1424
def _get_real_transport(self):
1425
# if we try vfs access, return the real branch's vfs transport
1427
return self._real_branch._transport
1429
_transport = property(_get_real_transport)
1432
return "%s(%s)" % (self.__class__.__name__, self.base)
1436
def _ensure_real(self):
1437
"""Ensure that there is a _real_branch set.
1439
Used before calls to self._real_branch.
1441
if self._real_branch is None:
1442
if not vfs.vfs_enabled():
1443
raise AssertionError('smart server vfs must be enabled '
1444
'to use vfs implementation')
1445
self.bzrdir._ensure_real()
1446
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1447
if self.repository._real_repository is None:
1448
# Give the remote repository the matching real repo.
1449
real_repo = self._real_branch.repository
1450
if isinstance(real_repo, RemoteRepository):
1451
real_repo._ensure_real()
1452
real_repo = real_repo._real_repository
1453
self.repository._set_real_repository(real_repo)
1454
# Give the real branch the remote repository to let fast-pathing
1456
self._real_branch.repository = self.repository
1457
if self._lock_mode == 'r':
1458
self._real_branch.lock_read()
1459
elif self._lock_mode == 'w':
1460
self._real_branch.lock_write(token=self._lock_token)
1462
def _translate_error(self, err, **context):
1463
self.repository._translate_error(err, branch=self, **context)
1465
def _clear_cached_state(self):
1466
super(RemoteBranch, self)._clear_cached_state()
1467
if self._real_branch is not None:
1468
self._real_branch._clear_cached_state()
1470
def _clear_cached_state_of_remote_branch_only(self):
1471
"""Like _clear_cached_state, but doesn't clear the cache of
1474
This is useful when falling back to calling a method of
1475
self._real_branch that changes state. In that case the underlying
1476
branch changes, so we need to invalidate this RemoteBranch's cache of
1477
it. However, there's no need to invalidate the _real_branch's cache
1478
too, in fact doing so might harm performance.
1480
super(RemoteBranch, self)._clear_cached_state()
1483
def control_files(self):
1484
# Defer actually creating RemoteBranchLockableFiles until its needed,
1485
# because it triggers an _ensure_real that we otherwise might not need.
1486
if self._control_files is None:
1487
self._control_files = RemoteBranchLockableFiles(
1488
self.bzrdir, self._client)
1489
return self._control_files
1491
def _get_checkout_format(self):
1493
return self._real_branch._get_checkout_format()
1495
def get_physical_lock_status(self):
1496
"""See Branch.get_physical_lock_status()."""
1497
# should be an API call to the server, as branches must be lockable.
1499
return self._real_branch.get_physical_lock_status()
1501
def get_stacked_on_url(self):
1502
"""Get the URL this branch is stacked against.
1504
:raises NotStacked: If the branch is not stacked.
1505
:raises UnstackableBranchFormat: If the branch does not support
1507
:raises UnstackableRepositoryFormat: If the repository does not support
1511
# there may not be a repository yet, so we can't use
1512
# self._translate_error, so we can't use self._call either.
1513
response = self._client.call('Branch.get_stacked_on_url',
1514
self._remote_path())
1515
except errors.ErrorFromSmartServer, err:
1516
# there may not be a repository yet, so we can't call through
1517
# its _translate_error
1518
_translate_error(err, branch=self)
1519
except errors.UnknownSmartMethod, err:
1521
return self._real_branch.get_stacked_on_url()
1522
if response[0] != 'ok':
1523
raise errors.UnexpectedSmartServerResponse(response)
1526
def lock_read(self):
1527
self.repository.lock_read()
1528
if not self._lock_mode:
1529
self._lock_mode = 'r'
1530
self._lock_count = 1
1531
if self._real_branch is not None:
1532
self._real_branch.lock_read()
1534
self._lock_count += 1
1536
def _remote_lock_write(self, token):
1538
branch_token = repo_token = ''
1540
branch_token = token
1541
repo_token = self.repository.lock_write()
1542
self.repository.unlock()
1543
err_context = {'token': token}
1544
response = self._call(
1545
'Branch.lock_write', self._remote_path(), branch_token,
1546
repo_token or '', **err_context)
1547
if response[0] != 'ok':
1548
raise errors.UnexpectedSmartServerResponse(response)
1549
ok, branch_token, repo_token = response
1550
return branch_token, repo_token
1552
def lock_write(self, token=None):
1553
if not self._lock_mode:
1554
# Lock the branch and repo in one remote call.
1555
remote_tokens = self._remote_lock_write(token)
1556
self._lock_token, self._repo_lock_token = remote_tokens
1557
if not self._lock_token:
1558
raise SmartProtocolError('Remote server did not return a token!')
1559
# Tell the self.repository object that it is locked.
1560
self.repository.lock_write(
1561
self._repo_lock_token, _skip_rpc=True)
1563
if self._real_branch is not None:
1564
self._real_branch.lock_write(token=self._lock_token)
1565
if token is not None:
1566
self._leave_lock = True
1568
self._leave_lock = False
1569
self._lock_mode = 'w'
1570
self._lock_count = 1
1571
elif self._lock_mode == 'r':
1572
raise errors.ReadOnlyTransaction
1574
if token is not None:
1575
# A token was given to lock_write, and we're relocking, so
1576
# check that the given token actually matches the one we
1578
if token != self._lock_token:
1579
raise errors.TokenMismatch(token, self._lock_token)
1580
self._lock_count += 1
1581
# Re-lock the repository too.
1582
self.repository.lock_write(self._repo_lock_token)
1583
return self._lock_token or None
1585
def _unlock(self, branch_token, repo_token):
1586
err_context = {'token': str((branch_token, repo_token))}
1587
response = self._call(
1588
'Branch.unlock', self._remote_path(), branch_token,
1589
repo_token or '', **err_context)
1590
if response == ('ok',):
1592
raise errors.UnexpectedSmartServerResponse(response)
1596
self._lock_count -= 1
1597
if not self._lock_count:
1598
self._clear_cached_state()
1599
mode = self._lock_mode
1600
self._lock_mode = None
1601
if self._real_branch is not None:
1602
if (not self._leave_lock and mode == 'w' and
1603
self._repo_lock_token):
1604
# If this RemoteBranch will remove the physical lock
1605
# for the repository, make sure the _real_branch
1606
# doesn't do it first. (Because the _real_branch's
1607
# repository is set to be the RemoteRepository.)
1608
self._real_branch.repository.leave_lock_in_place()
1609
self._real_branch.unlock()
1611
# Only write-locked branched need to make a remote method
1612
# call to perfom the unlock.
1614
if not self._lock_token:
1615
raise AssertionError('Locked, but no token!')
1616
branch_token = self._lock_token
1617
repo_token = self._repo_lock_token
1618
self._lock_token = None
1619
self._repo_lock_token = None
1620
if not self._leave_lock:
1621
self._unlock(branch_token, repo_token)
1623
self.repository.unlock()
1625
def break_lock(self):
1627
return self._real_branch.break_lock()
1629
def leave_lock_in_place(self):
1630
if not self._lock_token:
1631
raise NotImplementedError(self.leave_lock_in_place)
1632
self._leave_lock = True
1634
def dont_leave_lock_in_place(self):
1635
if not self._lock_token:
1636
raise NotImplementedError(self.dont_leave_lock_in_place)
1637
self._leave_lock = False
1639
def _last_revision_info(self):
1640
response = self._call('Branch.last_revision_info', self._remote_path())
1641
if response[0] != 'ok':
1642
raise SmartProtocolError('unexpected response code %s' % (response,))
1643
revno = int(response[1])
1644
last_revision = response[2]
1645
return (revno, last_revision)
1647
def _gen_revision_history(self):
1648
"""See Branch._gen_revision_history()."""
1649
response_tuple, response_handler = self._call_expecting_body(
1650
'Branch.revision_history', self._remote_path())
1651
if response_tuple[0] != 'ok':
1652
raise errors.UnexpectedSmartServerResponse(response_tuple)
1653
result = response_handler.read_body_bytes().split('\x00')
1658
def _remote_path(self):
1659
return self.bzrdir._path_for_remote_call(self._client)
1661
def _set_last_revision_descendant(self, revision_id, other_branch,
1662
allow_diverged=False, allow_overwrite_descendant=False):
1663
err_context = {'other_branch': other_branch}
1664
response = self._call('Branch.set_last_revision_ex',
1665
self._remote_path(), self._lock_token, self._repo_lock_token,
1666
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1668
self._clear_cached_state()
1669
if len(response) != 3 and response[0] != 'ok':
1670
raise errors.UnexpectedSmartServerResponse(response)
1671
new_revno, new_revision_id = response[1:]
1672
self._last_revision_info_cache = new_revno, new_revision_id
1673
if self._real_branch is not None:
1674
cache = new_revno, new_revision_id
1675
self._real_branch._last_revision_info_cache = cache
1677
def _set_last_revision(self, revision_id):
1678
self._clear_cached_state()
1679
response = self._call('Branch.set_last_revision',
1680
self._remote_path(), self._lock_token, self._repo_lock_token,
1682
if response != ('ok',):
1683
raise errors.UnexpectedSmartServerResponse(response)
1686
def set_revision_history(self, rev_history):
1687
# Send just the tip revision of the history; the server will generate
1688
# the full history from that. If the revision doesn't exist in this
1689
# branch, NoSuchRevision will be raised.
1690
if rev_history == []:
1693
rev_id = rev_history[-1]
1694
self._set_last_revision(rev_id)
1695
self._cache_revision_history(rev_history)
1697
def get_parent(self):
1699
return self._real_branch.get_parent()
1701
def set_parent(self, url):
1703
return self._real_branch.set_parent(url)
1705
def set_stacked_on_url(self, stacked_location):
1706
"""Set the URL this branch is stacked against.
1708
:raises UnstackableBranchFormat: If the branch does not support
1710
:raises UnstackableRepositoryFormat: If the repository does not support
1714
return self._real_branch.set_stacked_on_url(stacked_location)
1716
def sprout(self, to_bzrdir, revision_id=None):
1717
branch_format = to_bzrdir._format._branch_format
1718
if (branch_format is None or
1719
isinstance(branch_format, RemoteBranchFormat)):
1720
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1721
# implies the same thing), but RemoteBranches can't be created at
1722
# arbitrary URLs. So create a branch in the same format as
1723
# _real_branch instead.
1724
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1725
# to_bzrdir.create_branch to create a RemoteBranch after all...
1727
result = self._real_branch._format.initialize(to_bzrdir)
1728
self.copy_content_into(result, revision_id=revision_id)
1729
result.set_parent(self.bzrdir.root_transport.base)
1731
result = branch.Branch.sprout(
1732
self, to_bzrdir, revision_id=revision_id)
1736
def pull(self, source, overwrite=False, stop_revision=None,
1738
self._clear_cached_state_of_remote_branch_only()
1740
return self._real_branch.pull(
1741
source, overwrite=overwrite, stop_revision=stop_revision,
1742
_override_hook_target=self, **kwargs)
1745
def push(self, target, overwrite=False, stop_revision=None):
1747
return self._real_branch.push(
1748
target, overwrite=overwrite, stop_revision=stop_revision,
1749
_override_hook_source_branch=self)
1751
def is_locked(self):
1752
return self._lock_count >= 1
1755
def revision_id_to_revno(self, revision_id):
1757
return self._real_branch.revision_id_to_revno(revision_id)
1760
def set_last_revision_info(self, revno, revision_id):
1761
revision_id = ensure_null(revision_id)
1763
response = self._call('Branch.set_last_revision_info',
1764
self._remote_path(), self._lock_token, self._repo_lock_token,
1765
str(revno), revision_id)
1766
except errors.UnknownSmartMethod:
1768
self._clear_cached_state_of_remote_branch_only()
1769
self._real_branch.set_last_revision_info(revno, revision_id)
1770
self._last_revision_info_cache = revno, revision_id
1772
if response == ('ok',):
1773
self._clear_cached_state()
1774
self._last_revision_info_cache = revno, revision_id
1775
# Update the _real_branch's cache too.
1776
if self._real_branch is not None:
1777
cache = self._last_revision_info_cache
1778
self._real_branch._last_revision_info_cache = cache
1780
raise errors.UnexpectedSmartServerResponse(response)
1783
def generate_revision_history(self, revision_id, last_rev=None,
1785
medium = self._client._medium
1786
if not medium._is_remote_before((1, 6)):
1788
self._set_last_revision_descendant(revision_id, other_branch,
1789
allow_diverged=True, allow_overwrite_descendant=True)
1791
except errors.UnknownSmartMethod:
1792
medium._remember_remote_is_before((1, 6))
1793
self._clear_cached_state_of_remote_branch_only()
1795
self._real_branch.generate_revision_history(
1796
revision_id, last_rev=last_rev, other_branch=other_branch)
1801
return self._real_branch.tags
1803
def set_push_location(self, location):
1805
return self._real_branch.set_push_location(location)
1808
def update_revisions(self, other, stop_revision=None, overwrite=False,
1810
"""See Branch.update_revisions."""
1813
if stop_revision is None:
1814
stop_revision = other.last_revision()
1815
if revision.is_null(stop_revision):
1816
# if there are no commits, we're done.
1818
self.fetch(other, stop_revision)
1821
# Just unconditionally set the new revision. We don't care if
1822
# the branches have diverged.
1823
self._set_last_revision(stop_revision)
1825
medium = self._client._medium
1826
if not medium._is_remote_before((1, 6)):
1828
self._set_last_revision_descendant(stop_revision, other)
1830
except errors.UnknownSmartMethod:
1831
medium._remember_remote_is_before((1, 6))
1832
# Fallback for pre-1.6 servers: check for divergence
1833
# client-side, then do _set_last_revision.
1834
last_rev = revision.ensure_null(self.last_revision())
1836
graph = self.repository.get_graph()
1837
if self._check_if_descendant_or_diverged(
1838
stop_revision, last_rev, graph, other):
1839
# stop_revision is a descendant of last_rev, but we aren't
1840
# overwriting, so we're done.
1842
self._set_last_revision(stop_revision)
1847
def _extract_tar(tar, to_dir):
1848
"""Extract all the contents of a tarfile object.
1850
A replacement for extractall, which is not present in python2.4
1853
tar.extract(tarinfo, to_dir)
1856
def _translate_error(err, **context):
1857
"""Translate an ErrorFromSmartServer into a more useful error.
1859
Possible context keys:
1867
If the error from the server doesn't match a known pattern, then
1868
UnknownErrorFromSmartServer is raised.
1872
return context[name]
1873
except KeyError, key_err:
1874
mutter('Missing key %r in context %r', key_err.args[0], context)
1877
"""Get the path from the context if present, otherwise use first error
1881
return context['path']
1882
except KeyError, key_err:
1884
return err.error_args[0]
1885
except IndexError, idx_err:
1887
'Missing key %r in context %r', key_err.args[0], context)
1890
if err.error_verb == 'NoSuchRevision':
1891
raise NoSuchRevision(find('branch'), err.error_args[0])
1892
elif err.error_verb == 'nosuchrevision':
1893
raise NoSuchRevision(find('repository'), err.error_args[0])
1894
elif err.error_tuple == ('nobranch',):
1895
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1896
elif err.error_verb == 'norepository':
1897
raise errors.NoRepositoryPresent(find('bzrdir'))
1898
elif err.error_verb == 'LockContention':
1899
raise errors.LockContention('(remote lock)')
1900
elif err.error_verb == 'UnlockableTransport':
1901
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1902
elif err.error_verb == 'LockFailed':
1903
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1904
elif err.error_verb == 'TokenMismatch':
1905
raise errors.TokenMismatch(find('token'), '(remote token)')
1906
elif err.error_verb == 'Diverged':
1907
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1908
elif err.error_verb == 'TipChangeRejected':
1909
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1910
elif err.error_verb == 'UnstackableBranchFormat':
1911
raise errors.UnstackableBranchFormat(*err.error_args)
1912
elif err.error_verb == 'UnstackableRepositoryFormat':
1913
raise errors.UnstackableRepositoryFormat(*err.error_args)
1914
elif err.error_verb == 'NotStacked':
1915
raise errors.NotStacked(branch=find('branch'))
1916
elif err.error_verb == 'PermissionDenied':
1918
if len(err.error_args) >= 2:
1919
extra = err.error_args[1]
1922
raise errors.PermissionDenied(path, extra=extra)
1923
elif err.error_verb == 'ReadError':
1925
raise errors.ReadError(path)
1926
elif err.error_verb == 'NoSuchFile':
1928
raise errors.NoSuchFile(path)
1929
elif err.error_verb == 'FileExists':
1930
raise errors.FileExists(err.error_args[0])
1931
elif err.error_verb == 'DirectoryNotEmpty':
1932
raise errors.DirectoryNotEmpty(err.error_args[0])
1933
elif err.error_verb == 'ShortReadvError':
1934
args = err.error_args
1935
raise errors.ShortReadvError(
1936
args[0], int(args[1]), int(args[2]), int(args[3]))
1937
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1938
encoding = str(err.error_args[0]) # encoding must always be a string
1939
val = err.error_args[1]
1940
start = int(err.error_args[2])
1941
end = int(err.error_args[3])
1942
reason = str(err.error_args[4]) # reason must always be a string
1943
if val.startswith('u:'):
1944
val = val[2:].decode('utf-8')
1945
elif val.startswith('s:'):
1946
val = val[2:].decode('base64')
1947
if err.error_verb == 'UnicodeDecodeError':
1948
raise UnicodeDecodeError(encoding, val, start, end, reason)
1949
elif err.error_verb == 'UnicodeEncodeError':
1950
raise UnicodeEncodeError(encoding, val, start, end, reason)
1951
elif err.error_verb == 'ReadOnlyError':
1952
raise errors.TransportNotPossible('readonly transport')
1953
raise errors.UnknownErrorFromSmartServer(err)