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.
33
from bzrlib.branch import BranchReferenceFormat
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
from bzrlib.errors import (
40
from bzrlib.lockable_files import LockableFiles
41
from bzrlib.smart import client, vfs
42
from bzrlib.revision import ensure_null, NULL_REVISION
43
from bzrlib.trace import mutter, note, warning
46
class _RpcHelper(object):
47
"""Mixin class that helps with issuing RPCs."""
49
def _call(self, method, *args, **err_context):
51
return self._client.call(method, *args)
52
except errors.ErrorFromSmartServer, err:
53
self._translate_error(err, **err_context)
55
def _call_expecting_body(self, method, *args, **err_context):
57
return self._client.call_expecting_body(method, *args)
58
except errors.ErrorFromSmartServer, err:
59
self._translate_error(err, **err_context)
61
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
64
return self._client.call_with_body_bytes_expecting_body(
65
method, args, body_bytes)
66
except errors.ErrorFromSmartServer, err:
67
self._translate_error(err, **err_context)
69
# Note: RemoteBzrDirFormat is in bzrdir.py
71
class RemoteBzrDir(BzrDir, _RpcHelper):
72
"""Control directory on a remote server, accessed via bzr:// or similar."""
74
def __init__(self, transport, _client=None):
75
"""Construct a RemoteBzrDir.
77
:param _client: Private parameter for testing. Disables probing and the
80
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
81
# this object holds a delegated bzrdir that uses file-level operations
82
# to talk to the other side
83
self._real_bzrdir = None
86
medium = transport.get_smart_medium()
87
self._client = client._SmartClient(medium)
89
self._client = _client
92
path = self._path_for_remote_call(self._client)
93
response = self._call('BzrDir.open', path)
94
if response not in [('yes',), ('no',)]:
95
raise errors.UnexpectedSmartServerResponse(response)
96
if response == ('no',):
97
raise errors.NotBranchError(path=transport.base)
99
def _ensure_real(self):
100
"""Ensure that there is a _real_bzrdir set.
102
Used before calls to self._real_bzrdir.
104
if not self._real_bzrdir:
105
self._real_bzrdir = BzrDir.open_from_transport(
106
self.root_transport, _server_formats=False)
108
def _translate_error(self, err, **context):
109
_translate_error(err, bzrdir=self, **context)
111
def cloning_metadir(self, stacked=False):
113
return self._real_bzrdir.cloning_metadir(stacked)
115
def create_repository(self, shared=False):
117
self._real_bzrdir.create_repository(shared=shared)
118
return self.open_repository()
120
def destroy_repository(self):
121
"""See BzrDir.destroy_repository"""
123
self._real_bzrdir.destroy_repository()
125
def create_branch(self):
127
real_branch = self._real_bzrdir.create_branch()
128
return RemoteBranch(self, self.find_repository(), real_branch)
130
def destroy_branch(self):
131
"""See BzrDir.destroy_branch"""
133
self._real_bzrdir.destroy_branch()
135
def create_workingtree(self, revision_id=None, from_branch=None):
136
raise errors.NotLocalUrl(self.transport.base)
138
def find_branch_format(self):
139
"""Find the branch 'format' for this bzrdir.
141
This might be a synthetic object for e.g. RemoteBranch and SVN.
143
b = self.open_branch()
146
def get_branch_reference(self):
147
"""See BzrDir.get_branch_reference()."""
148
path = self._path_for_remote_call(self._client)
149
response = self._call('BzrDir.open_branch', path)
150
if response[0] == 'ok':
151
if response[1] == '':
152
# branch at this location.
155
# a branch reference, use the existing BranchReference logic.
158
raise errors.UnexpectedSmartServerResponse(response)
160
def _get_tree_branch(self):
161
"""See BzrDir._get_tree_branch()."""
162
return None, self.open_branch()
164
def open_branch(self, _unsupported=False):
166
raise NotImplementedError('unsupported flag support not implemented yet.')
167
reference_url = self.get_branch_reference()
168
if reference_url is None:
169
# branch at this location.
170
return RemoteBranch(self, self.find_repository())
172
# a branch reference, use the existing BranchReference logic.
173
format = BranchReferenceFormat()
174
return format.open(self, _found=True, location=reference_url)
176
def open_repository(self):
177
path = self._path_for_remote_call(self._client)
178
verb = 'BzrDir.find_repositoryV2'
180
response = self._call(verb, path)
181
except errors.UnknownSmartMethod:
182
verb = 'BzrDir.find_repository'
183
response = self._call(verb, path)
184
if response[0] != 'ok':
185
raise errors.UnexpectedSmartServerResponse(response)
186
if verb == 'BzrDir.find_repository':
187
# servers that don't support the V2 method don't support external
189
response = response + ('no', )
190
if not (len(response) == 5):
191
raise SmartProtocolError('incorrect response length %s' % (response,))
192
if response[1] == '':
193
format = RemoteRepositoryFormat()
194
format.rich_root_data = (response[2] == 'yes')
195
format.supports_tree_reference = (response[3] == 'yes')
196
# No wire format to check this yet.
197
format.supports_external_lookups = (response[4] == 'yes')
198
# Used to support creating a real format instance when needed.
199
format._creating_bzrdir = self
200
return RemoteRepository(self, format)
202
raise errors.NoRepositoryPresent(self)
204
def open_workingtree(self, recommend_upgrade=True):
206
if self._real_bzrdir.has_workingtree():
207
raise errors.NotLocalUrl(self.root_transport)
209
raise errors.NoWorkingTree(self.root_transport.base)
211
def _path_for_remote_call(self, client):
212
"""Return the path to be used for this bzrdir in a remote call."""
213
return client.remote_path_from_transport(self.root_transport)
215
def get_branch_transport(self, branch_format):
217
return self._real_bzrdir.get_branch_transport(branch_format)
219
def get_repository_transport(self, repository_format):
221
return self._real_bzrdir.get_repository_transport(repository_format)
223
def get_workingtree_transport(self, workingtree_format):
225
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
227
def can_convert_format(self):
228
"""Upgrading of remote bzrdirs is not supported yet."""
231
def needs_format_conversion(self, format=None):
232
"""Upgrading of remote bzrdirs is not supported yet."""
235
def clone(self, url, revision_id=None, force_new_repo=False,
236
preserve_stacking=False):
238
return self._real_bzrdir.clone(url, revision_id=revision_id,
239
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
241
def get_config(self):
243
return self._real_bzrdir.get_config()
246
class RemoteRepositoryFormat(repository.RepositoryFormat):
247
"""Format for repositories accessed over a _SmartClient.
249
Instances of this repository are represented by RemoteRepository
252
The RemoteRepositoryFormat is parameterized during construction
253
to reflect the capabilities of the real, remote format. Specifically
254
the attributes rich_root_data and supports_tree_reference are set
255
on a per instance basis, and are not set (and should not be) at
259
_matchingbzrdir = RemoteBzrDirFormat()
261
def initialize(self, a_bzrdir, shared=False):
262
if not isinstance(a_bzrdir, RemoteBzrDir):
263
prior_repo = self._creating_bzrdir.open_repository()
264
prior_repo._ensure_real()
265
return prior_repo._real_repository._format.initialize(
266
a_bzrdir, shared=shared)
267
return a_bzrdir.create_repository(shared=shared)
269
def open(self, a_bzrdir):
270
if not isinstance(a_bzrdir, RemoteBzrDir):
271
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
272
return a_bzrdir.open_repository()
274
def get_format_description(self):
275
return 'bzr remote repository'
277
def __eq__(self, other):
278
return self.__class__ == other.__class__
280
def check_conversion_target(self, target_format):
281
if self.rich_root_data and not target_format.rich_root_data:
282
raise errors.BadConversionTarget(
283
'Does not support rich root data.', target_format)
284
if (self.supports_tree_reference and
285
not getattr(target_format, 'supports_tree_reference', False)):
286
raise errors.BadConversionTarget(
287
'Does not support nested trees', target_format)
290
class RemoteRepository(_RpcHelper):
291
"""Repository accessed over rpc.
293
For the moment most operations are performed using local transport-backed
297
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
298
"""Create a RemoteRepository instance.
300
:param remote_bzrdir: The bzrdir hosting this repository.
301
:param format: The RemoteFormat object to use.
302
:param real_repository: If not None, a local implementation of the
303
repository logic for the repository, usually accessing the data
305
:param _client: Private testing parameter - override the smart client
306
to be used by the repository.
309
self._real_repository = real_repository
311
self._real_repository = None
312
self.bzrdir = remote_bzrdir
314
self._client = remote_bzrdir._client
316
self._client = _client
317
self._format = format
318
self._lock_mode = None
319
self._lock_token = None
321
self._leave_lock = False
322
debug_cache = ('hpss' in debug.debug_flags)
323
self._unstacked_provider = graph.CachingParentsProvider(
324
get_parent_map=self._get_parent_map_rpc, debug=debug_cache)
325
self._unstacked_provider.disable_cache()
327
# These depend on the actual remote format, so force them off for
328
# maximum compatibility. XXX: In future these should depend on the
329
# remote repository instance, but this is irrelevant until we perform
330
# reconcile via an RPC call.
331
self._reconcile_does_inventory_gc = False
332
self._reconcile_fixes_text_parents = False
333
self._reconcile_backsup_inventory = False
334
self.base = self.bzrdir.transport.base
335
# Additional places to query for data.
336
self._fallback_repositories = []
339
return "%s(%s)" % (self.__class__.__name__, self.base)
343
def abort_write_group(self, suppress_errors=False):
344
"""Complete a write group on the decorated repository.
346
Smart methods peform operations in a single step so this api
347
is not really applicable except as a compatibility thunk
348
for older plugins that don't use e.g. the CommitBuilder
351
:param suppress_errors: see Repository.abort_write_group.
354
return self._real_repository.abort_write_group(
355
suppress_errors=suppress_errors)
357
def commit_write_group(self):
358
"""Complete a write group on the decorated repository.
360
Smart methods peform operations in a single step so this api
361
is not really applicable except as a compatibility thunk
362
for older plugins that don't use e.g. the CommitBuilder
366
return self._real_repository.commit_write_group()
368
def _ensure_real(self):
369
"""Ensure that there is a _real_repository set.
371
Used before calls to self._real_repository.
373
if self._real_repository is None:
374
self.bzrdir._ensure_real()
375
self._set_real_repository(
376
self.bzrdir._real_bzrdir.open_repository())
378
def _translate_error(self, err, **context):
379
self.bzrdir._translate_error(err, repository=self, **context)
381
def find_text_key_references(self):
382
"""Find the text key references within the repository.
384
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
385
revision_ids. Each altered file-ids has the exact revision_ids that
386
altered it listed explicitly.
387
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
388
to whether they were referred to by the inventory of the
389
revision_id that they contain. The inventory texts from all present
390
revision ids are assessed to generate this report.
393
return self._real_repository.find_text_key_references()
395
def _generate_text_key_index(self):
396
"""Generate a new text key index for the repository.
398
This is an expensive function that will take considerable time to run.
400
:return: A dict mapping (file_id, revision_id) tuples to a list of
401
parents, also (file_id, revision_id) tuples.
404
return self._real_repository._generate_text_key_index()
406
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
407
def get_revision_graph(self, revision_id=None):
408
"""See Repository.get_revision_graph()."""
409
return self._get_revision_graph(revision_id)
411
def _get_revision_graph(self, revision_id):
412
"""Private method for using with old (< 1.2) servers to fallback."""
413
if revision_id is None:
415
elif revision.is_null(revision_id):
418
path = self.bzrdir._path_for_remote_call(self._client)
419
response = self._call_expecting_body(
420
'Repository.get_revision_graph', path, revision_id)
421
response_tuple, response_handler = response
422
if response_tuple[0] != 'ok':
423
raise errors.UnexpectedSmartServerResponse(response_tuple)
424
coded = response_handler.read_body_bytes()
426
# no revisions in this repository!
428
lines = coded.split('\n')
431
d = tuple(line.split())
432
revision_graph[d[0]] = d[1:]
434
return revision_graph
436
def has_revision(self, revision_id):
437
"""See Repository.has_revision()."""
438
if revision_id == NULL_REVISION:
439
# The null revision is always present.
441
path = self.bzrdir._path_for_remote_call(self._client)
442
response = self._call('Repository.has_revision', path, revision_id)
443
if response[0] not in ('yes', 'no'):
444
raise errors.UnexpectedSmartServerResponse(response)
445
if response[0] == 'yes':
447
for fallback_repo in self._fallback_repositories:
448
if fallback_repo.has_revision(revision_id):
452
def has_revisions(self, revision_ids):
453
"""See Repository.has_revisions()."""
454
# FIXME: This does many roundtrips, particularly when there are
455
# fallback repositories. -- mbp 20080905
457
for revision_id in revision_ids:
458
if self.has_revision(revision_id):
459
result.add(revision_id)
462
def has_same_location(self, other):
463
return (self.__class__ == other.__class__ and
464
self.bzrdir.transport.base == other.bzrdir.transport.base)
466
def get_graph(self, other_repository=None):
467
"""Return the graph for this repository format"""
468
parents_provider = self._make_parents_provider()
469
if (other_repository is not None and
470
other_repository.bzrdir.transport.base !=
471
self.bzrdir.transport.base):
472
parents_provider = graph._StackedParentsProvider(
473
[self._unstacked_provider,
474
other_repository._make_parents_provider()])
475
return graph.Graph(parents_provider)
477
def gather_stats(self, revid=None, committers=None):
478
"""See Repository.gather_stats()."""
479
path = self.bzrdir._path_for_remote_call(self._client)
480
# revid can be None to indicate no revisions, not just NULL_REVISION
481
if revid is None or revision.is_null(revid):
485
if committers is None or not committers:
486
fmt_committers = 'no'
488
fmt_committers = 'yes'
489
response_tuple, response_handler = self._call_expecting_body(
490
'Repository.gather_stats', path, fmt_revid, fmt_committers)
491
if response_tuple[0] != 'ok':
492
raise errors.UnexpectedSmartServerResponse(response_tuple)
494
body = response_handler.read_body_bytes()
496
for line in body.split('\n'):
499
key, val_text = line.split(':')
500
if key in ('revisions', 'size', 'committers'):
501
result[key] = int(val_text)
502
elif key in ('firstrev', 'latestrev'):
503
values = val_text.split(' ')[1:]
504
result[key] = (float(values[0]), long(values[1]))
508
def find_branches(self, using=False):
509
"""See Repository.find_branches()."""
510
# should be an API call to the server.
512
return self._real_repository.find_branches(using=using)
514
def get_physical_lock_status(self):
515
"""See Repository.get_physical_lock_status()."""
516
# should be an API call to the server.
518
return self._real_repository.get_physical_lock_status()
520
def is_in_write_group(self):
521
"""Return True if there is an open write group.
523
write groups are only applicable locally for the smart server..
525
if self._real_repository:
526
return self._real_repository.is_in_write_group()
529
return self._lock_count >= 1
532
"""See Repository.is_shared()."""
533
path = self.bzrdir._path_for_remote_call(self._client)
534
response = self._call('Repository.is_shared', path)
535
if response[0] not in ('yes', 'no'):
536
raise SmartProtocolError('unexpected response code %s' % (response,))
537
return response[0] == 'yes'
539
def is_write_locked(self):
540
return self._lock_mode == 'w'
543
# wrong eventually - want a local lock cache context
544
if not self._lock_mode:
545
self._lock_mode = 'r'
547
self._unstacked_provider.enable_cache(cache_misses=False)
548
if self._real_repository is not None:
549
self._real_repository.lock_read()
551
self._lock_count += 1
553
def _remote_lock_write(self, token):
554
path = self.bzrdir._path_for_remote_call(self._client)
557
err_context = {'token': token}
558
response = self._call('Repository.lock_write', path, token,
560
if response[0] == 'ok':
564
raise errors.UnexpectedSmartServerResponse(response)
566
def lock_write(self, token=None, _skip_rpc=False):
567
if not self._lock_mode:
569
if self._lock_token is not None:
570
if token != self._lock_token:
571
raise errors.TokenMismatch(token, self._lock_token)
572
self._lock_token = token
574
self._lock_token = self._remote_lock_write(token)
575
# if self._lock_token is None, then this is something like packs or
576
# svn where we don't get to lock the repo, or a weave style repository
577
# where we cannot lock it over the wire and attempts to do so will
579
if self._real_repository is not None:
580
self._real_repository.lock_write(token=self._lock_token)
581
if token is not None:
582
self._leave_lock = True
584
self._leave_lock = False
585
self._lock_mode = 'w'
587
self._unstacked_provider.enable_cache(cache_misses=False)
588
elif self._lock_mode == 'r':
589
raise errors.ReadOnlyError(self)
591
self._lock_count += 1
592
return self._lock_token or None
594
def leave_lock_in_place(self):
595
if not self._lock_token:
596
raise NotImplementedError(self.leave_lock_in_place)
597
self._leave_lock = True
599
def dont_leave_lock_in_place(self):
600
if not self._lock_token:
601
raise NotImplementedError(self.dont_leave_lock_in_place)
602
self._leave_lock = False
604
def _set_real_repository(self, repository):
605
"""Set the _real_repository for this repository.
607
:param repository: The repository to fallback to for non-hpss
608
implemented operations.
610
if self._real_repository is not None:
611
raise AssertionError('_real_repository is already set')
612
if isinstance(repository, RemoteRepository):
613
raise AssertionError()
614
self._real_repository = repository
615
for fb in self._fallback_repositories:
616
self._real_repository.add_fallback_repository(fb)
617
if self._lock_mode == 'w':
618
# if we are already locked, the real repository must be able to
619
# acquire the lock with our token.
620
self._real_repository.lock_write(self._lock_token)
621
elif self._lock_mode == 'r':
622
self._real_repository.lock_read()
624
def start_write_group(self):
625
"""Start a write group on the decorated repository.
627
Smart methods peform operations in a single step so this api
628
is not really applicable except as a compatibility thunk
629
for older plugins that don't use e.g. the CommitBuilder
633
return self._real_repository.start_write_group()
635
def _unlock(self, token):
636
path = self.bzrdir._path_for_remote_call(self._client)
638
# with no token the remote repository is not persistently locked.
640
err_context = {'token': token}
641
response = self._call('Repository.unlock', path, token,
643
if response == ('ok',):
646
raise errors.UnexpectedSmartServerResponse(response)
649
self._lock_count -= 1
650
if self._lock_count > 0:
652
self._unstacked_provider.disable_cache()
653
old_mode = self._lock_mode
654
self._lock_mode = None
656
# The real repository is responsible at present for raising an
657
# exception if it's in an unfinished write group. However, it
658
# normally will *not* actually remove the lock from disk - that's
659
# done by the server on receiving the Repository.unlock call.
660
# This is just to let the _real_repository stay up to date.
661
if self._real_repository is not None:
662
self._real_repository.unlock()
664
# The rpc-level lock should be released even if there was a
665
# problem releasing the vfs-based lock.
667
# Only write-locked repositories need to make a remote method
668
# call to perfom the unlock.
669
old_token = self._lock_token
670
self._lock_token = None
671
if not self._leave_lock:
672
self._unlock(old_token)
674
def break_lock(self):
675
# should hand off to the network
677
return self._real_repository.break_lock()
679
def _get_tarball(self, compression):
680
"""Return a TemporaryFile containing a repository tarball.
682
Returns None if the server does not support sending tarballs.
685
path = self.bzrdir._path_for_remote_call(self._client)
687
response, protocol = self._call_expecting_body(
688
'Repository.tarball', path, compression)
689
except errors.UnknownSmartMethod:
690
protocol.cancel_read_body()
692
if response[0] == 'ok':
693
# Extract the tarball and return it
694
t = tempfile.NamedTemporaryFile()
695
# TODO: rpc layer should read directly into it...
696
t.write(protocol.read_body_bytes())
699
raise errors.UnexpectedSmartServerResponse(response)
701
def sprout(self, to_bzrdir, revision_id=None):
702
# TODO: Option to control what format is created?
704
dest_repo = self._real_repository._format.initialize(to_bzrdir,
706
dest_repo.fetch(self, revision_id=revision_id)
709
### These methods are just thin shims to the VFS object for now.
711
def revision_tree(self, revision_id):
713
return self._real_repository.revision_tree(revision_id)
715
def get_serializer_format(self):
717
return self._real_repository.get_serializer_format()
719
def get_commit_builder(self, branch, parents, config, timestamp=None,
720
timezone=None, committer=None, revprops=None,
722
# FIXME: It ought to be possible to call this without immediately
723
# triggering _ensure_real. For now it's the easiest thing to do.
725
real_repo = self._real_repository
726
builder = real_repo.get_commit_builder(branch, parents,
727
config, timestamp=timestamp, timezone=timezone,
728
committer=committer, revprops=revprops, revision_id=revision_id)
731
def add_fallback_repository(self, repository):
732
"""Add a repository to use for looking up data not held locally.
734
:param repository: A repository.
736
# XXX: At the moment the RemoteRepository will allow fallbacks
737
# unconditionally - however, a _real_repository will usually exist,
738
# and may raise an error if it's not accommodated by the underlying
739
# format. Eventually we should check when opening the repository
740
# whether it's willing to allow them or not.
742
# We need to accumulate additional repositories here, to pass them in
744
self._fallback_repositories.append(repository)
745
# They are also seen by the fallback repository. If it doesn't exist
746
# yet they'll be added then. This implicitly copies them.
749
def add_inventory(self, revid, inv, parents):
751
return self._real_repository.add_inventory(revid, inv, parents)
753
def add_revision(self, rev_id, rev, inv=None, config=None):
755
return self._real_repository.add_revision(
756
rev_id, rev, inv=inv, config=config)
759
def get_inventory(self, revision_id):
761
return self._real_repository.get_inventory(revision_id)
763
def iter_inventories(self, revision_ids):
765
return self._real_repository.iter_inventories(revision_ids)
768
def get_revision(self, revision_id):
770
return self._real_repository.get_revision(revision_id)
772
def get_transaction(self):
774
return self._real_repository.get_transaction()
777
def clone(self, a_bzrdir, revision_id=None):
779
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
781
def make_working_trees(self):
782
"""See Repository.make_working_trees"""
784
return self._real_repository.make_working_trees()
786
def revision_ids_to_search_result(self, result_set):
787
"""Convert a set of revision ids to a graph SearchResult."""
788
result_parents = set()
789
for parents in self.get_graph().get_parent_map(
790
result_set).itervalues():
791
result_parents.update(parents)
792
included_keys = result_set.intersection(result_parents)
793
start_keys = result_set.difference(included_keys)
794
exclude_keys = result_parents.difference(result_set)
795
result = graph.SearchResult(start_keys, exclude_keys,
796
len(result_set), result_set)
800
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
801
"""Return the revision ids that other has that this does not.
803
These are returned in topological order.
805
revision_id: only return revision ids included by revision_id.
807
return repository.InterRepository.get(
808
other, self).search_missing_revision_ids(revision_id, find_ghosts)
810
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
811
# Not delegated to _real_repository so that InterRepository.get has a
812
# chance to find an InterRepository specialised for RemoteRepository.
813
if self.has_same_location(source):
814
# check that last_revision is in 'from' and then return a
816
if (revision_id is not None and
817
not revision.is_null(revision_id)):
818
self.get_revision(revision_id)
820
inter = repository.InterRepository.get(source, self)
822
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
823
except NotImplementedError:
824
raise errors.IncompatibleRepositories(source, self)
826
def create_bundle(self, target, base, fileobj, format=None):
828
self._real_repository.create_bundle(target, base, fileobj, format)
831
def get_ancestry(self, revision_id, topo_sorted=True):
833
return self._real_repository.get_ancestry(revision_id, topo_sorted)
835
def fileids_altered_by_revision_ids(self, revision_ids):
837
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
839
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
841
return self._real_repository._get_versioned_file_checker(
842
revisions, revision_versions_cache)
844
def iter_files_bytes(self, desired_files):
845
"""See Repository.iter_file_bytes.
848
return self._real_repository.iter_files_bytes(desired_files)
851
def _fetch_order(self):
852
"""Decorate the real repository for now.
854
In the long term getting this back from the remote repository as part
855
of open would be more efficient.
858
return self._real_repository._fetch_order
861
def _fetch_uses_deltas(self):
862
"""Decorate the real repository for now.
864
In the long term getting this back from the remote repository as part
865
of open would be more efficient.
868
return self._real_repository._fetch_uses_deltas
871
def _fetch_reconcile(self):
872
"""Decorate the real repository for now.
874
In the long term getting this back from the remote repository as part
875
of open would be more efficient.
878
return self._real_repository._fetch_reconcile
880
def get_parent_map(self, revision_ids):
881
"""See bzrlib.Graph.get_parent_map()."""
882
return self._make_parents_provider().get_parent_map(revision_ids)
884
def _get_parent_map_rpc(self, keys):
885
"""Helper for get_parent_map that performs the RPC."""
886
medium = self._client._medium
887
if medium._is_remote_before((1, 2)):
888
# We already found out that the server can't understand
889
# Repository.get_parent_map requests, so just fetch the whole
891
# XXX: Note that this will issue a deprecation warning. This is ok
892
# :- its because we're working with a deprecated server anyway, and
893
# the user will almost certainly have seen a warning about the
894
# server version already.
895
rg = self.get_revision_graph()
896
# There is an api discrepency between get_parent_map and
897
# get_revision_graph. Specifically, a "key:()" pair in
898
# get_revision_graph just means a node has no parents. For
899
# "get_parent_map" it means the node is a ghost. So fix up the
900
# graph to correct this.
901
# https://bugs.launchpad.net/bzr/+bug/214894
902
# There is one other "bug" which is that ghosts in
903
# get_revision_graph() are not returned at all. But we won't worry
904
# about that for now.
905
for node_id, parent_ids in rg.iteritems():
907
rg[node_id] = (NULL_REVISION,)
908
rg[NULL_REVISION] = ()
913
raise ValueError('get_parent_map(None) is not valid')
914
if NULL_REVISION in keys:
915
keys.discard(NULL_REVISION)
916
found_parents = {NULL_REVISION:()}
921
# TODO(Needs analysis): We could assume that the keys being requested
922
# from get_parent_map are in a breadth first search, so typically they
923
# will all be depth N from some common parent, and we don't have to
924
# have the server iterate from the root parent, but rather from the
925
# keys we're searching; and just tell the server the keyspace we
926
# already have; but this may be more traffic again.
928
# Transform self._parents_map into a search request recipe.
929
# TODO: Manage this incrementally to avoid covering the same path
930
# repeatedly. (The server will have to on each request, but the less
931
# work done the better).
932
parents_map = self._unstacked_provider.get_cached_map()
933
if parents_map is None:
934
# Repository is not locked, so there's no cache.
936
start_set = set(parents_map)
937
result_parents = set()
938
for parents in parents_map.itervalues():
939
result_parents.update(parents)
940
stop_keys = result_parents.difference(start_set)
941
included_keys = start_set.intersection(result_parents)
942
start_set.difference_update(included_keys)
943
recipe = (start_set, stop_keys, len(parents_map))
944
body = self._serialise_search_recipe(recipe)
945
path = self.bzrdir._path_for_remote_call(self._client)
947
if type(key) is not str:
949
"key %r not a plain string" % (key,))
950
verb = 'Repository.get_parent_map'
951
args = (path,) + tuple(keys)
953
response = self._call_with_body_bytes_expecting_body(
955
except errors.UnknownSmartMethod:
956
# Server does not support this method, so get the whole graph.
957
# Worse, we have to force a disconnection, because the server now
958
# doesn't realise it has a body on the wire to consume, so the
959
# only way to recover is to abandon the connection.
961
'Server is too old for fast get_parent_map, reconnecting. '
962
'(Upgrade the server to Bazaar 1.2 to avoid this)')
964
# To avoid having to disconnect repeatedly, we keep track of the
965
# fact the server doesn't understand remote methods added in 1.2.
966
medium._remember_remote_is_before((1, 2))
967
return self.get_revision_graph(None)
968
response_tuple, response_handler = response
969
if response_tuple[0] not in ['ok']:
970
response_handler.cancel_read_body()
971
raise errors.UnexpectedSmartServerResponse(response_tuple)
972
if response_tuple[0] == 'ok':
973
coded = bz2.decompress(response_handler.read_body_bytes())
977
lines = coded.split('\n')
980
d = tuple(line.split())
982
revision_graph[d[0]] = d[1:]
984
# No parents - so give the Graph result (NULL_REVISION,).
985
revision_graph[d[0]] = (NULL_REVISION,)
986
return revision_graph
989
def get_signature_text(self, revision_id):
991
return self._real_repository.get_signature_text(revision_id)
994
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
995
def get_revision_graph_with_ghosts(self, revision_ids=None):
997
return self._real_repository.get_revision_graph_with_ghosts(
998
revision_ids=revision_ids)
1001
def get_inventory_xml(self, revision_id):
1003
return self._real_repository.get_inventory_xml(revision_id)
1005
def deserialise_inventory(self, revision_id, xml):
1007
return self._real_repository.deserialise_inventory(revision_id, xml)
1009
def reconcile(self, other=None, thorough=False):
1011
return self._real_repository.reconcile(other=other, thorough=thorough)
1013
def all_revision_ids(self):
1015
return self._real_repository.all_revision_ids()
1018
def get_deltas_for_revisions(self, revisions):
1020
return self._real_repository.get_deltas_for_revisions(revisions)
1023
def get_revision_delta(self, revision_id):
1025
return self._real_repository.get_revision_delta(revision_id)
1028
def revision_trees(self, revision_ids):
1030
return self._real_repository.revision_trees(revision_ids)
1033
def get_revision_reconcile(self, revision_id):
1035
return self._real_repository.get_revision_reconcile(revision_id)
1038
def check(self, revision_ids=None):
1040
return self._real_repository.check(revision_ids=revision_ids)
1042
def copy_content_into(self, destination, revision_id=None):
1044
return self._real_repository.copy_content_into(
1045
destination, revision_id=revision_id)
1047
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1048
# get a tarball of the remote repository, and copy from that into the
1050
from bzrlib import osutils
1052
# TODO: Maybe a progress bar while streaming the tarball?
1053
note("Copying repository content as tarball...")
1054
tar_file = self._get_tarball('bz2')
1055
if tar_file is None:
1057
destination = to_bzrdir.create_repository()
1059
tar = tarfile.open('repository', fileobj=tar_file,
1061
tmpdir = osutils.mkdtemp()
1063
_extract_tar(tar, tmpdir)
1064
tmp_bzrdir = BzrDir.open(tmpdir)
1065
tmp_repo = tmp_bzrdir.open_repository()
1066
tmp_repo.copy_content_into(destination, revision_id)
1068
osutils.rmtree(tmpdir)
1072
# TODO: Suggestion from john: using external tar is much faster than
1073
# python's tarfile library, but it may not work on windows.
1076
def inventories(self):
1077
"""Decorate the real repository for now.
1079
In the long term a full blown network facility is needed to
1080
avoid creating a real repository object locally.
1083
return self._real_repository.inventories
1087
"""Compress the data within the repository.
1089
This is not currently implemented within the smart server.
1092
return self._real_repository.pack()
1095
def revisions(self):
1096
"""Decorate the real repository for now.
1098
In the short term this should become a real object to intercept graph
1101
In the long term a full blown network facility is needed.
1104
return self._real_repository.revisions
1106
def set_make_working_trees(self, new_value):
1108
self._real_repository.set_make_working_trees(new_value)
1111
def signatures(self):
1112
"""Decorate the real repository for now.
1114
In the long term a full blown network facility is needed to avoid
1115
creating a real repository object locally.
1118
return self._real_repository.signatures
1121
def sign_revision(self, revision_id, gpg_strategy):
1123
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1127
"""Decorate the real repository for now.
1129
In the long term a full blown network facility is needed to avoid
1130
creating a real repository object locally.
1133
return self._real_repository.texts
1136
def get_revisions(self, revision_ids):
1138
return self._real_repository.get_revisions(revision_ids)
1140
def supports_rich_root(self):
1142
return self._real_repository.supports_rich_root()
1144
def iter_reverse_revision_history(self, revision_id):
1146
return self._real_repository.iter_reverse_revision_history(revision_id)
1149
def _serializer(self):
1151
return self._real_repository._serializer
1153
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1155
return self._real_repository.store_revision_signature(
1156
gpg_strategy, plaintext, revision_id)
1158
def add_signature_text(self, revision_id, signature):
1160
return self._real_repository.add_signature_text(revision_id, signature)
1162
def has_signature_for_revision_id(self, revision_id):
1164
return self._real_repository.has_signature_for_revision_id(revision_id)
1166
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1168
return self._real_repository.item_keys_introduced_by(revision_ids,
1169
_files_pb=_files_pb)
1171
def revision_graph_can_have_wrong_parents(self):
1172
# The answer depends on the remote repo format.
1174
return self._real_repository.revision_graph_can_have_wrong_parents()
1176
def _find_inconsistent_revision_parents(self):
1178
return self._real_repository._find_inconsistent_revision_parents()
1180
def _check_for_inconsistent_revision_parents(self):
1182
return self._real_repository._check_for_inconsistent_revision_parents()
1184
def _make_parents_provider(self):
1185
providers = [self._unstacked_provider]
1186
providers.extend(r._make_parents_provider() for r in
1187
self._fallback_repositories)
1188
return graph._StackedParentsProvider(providers)
1190
def _serialise_search_recipe(self, recipe):
1191
"""Serialise a graph search recipe.
1193
:param recipe: A search recipe (start, stop, count).
1194
:return: Serialised bytes.
1196
start_keys = ' '.join(recipe[0])
1197
stop_keys = ' '.join(recipe[1])
1198
count = str(recipe[2])
1199
return '\n'.join((start_keys, stop_keys, count))
1202
path = self.bzrdir._path_for_remote_call(self._client)
1204
response = self._call('PackRepository.autopack', path)
1205
except errors.UnknownSmartMethod:
1207
self._real_repository._pack_collection.autopack()
1209
if self._real_repository is not None:
1210
# Reset the real repository's cache of pack names.
1211
# XXX: At some point we may be able to skip this and just rely on
1212
# the automatic retry logic to do the right thing, but for now we
1213
# err on the side of being correct rather than being optimal.
1214
self._real_repository._pack_collection.reload_pack_names()
1215
if response[0] != 'ok':
1216
raise errors.UnexpectedSmartServerResponse(response)
1219
class RemoteBranchLockableFiles(LockableFiles):
1220
"""A 'LockableFiles' implementation that talks to a smart server.
1222
This is not a public interface class.
1225
def __init__(self, bzrdir, _client):
1226
self.bzrdir = bzrdir
1227
self._client = _client
1228
self._need_find_modes = True
1229
LockableFiles.__init__(
1230
self, bzrdir.get_branch_transport(None),
1231
'lock', lockdir.LockDir)
1233
def _find_modes(self):
1234
# RemoteBranches don't let the client set the mode of control files.
1235
self._dir_mode = None
1236
self._file_mode = None
1239
class RemoteBranchFormat(branch.BranchFormat):
1242
super(RemoteBranchFormat, self).__init__()
1243
self._matchingbzrdir = RemoteBzrDirFormat()
1244
self._matchingbzrdir.set_branch_format(self)
1246
def __eq__(self, other):
1247
return (isinstance(other, RemoteBranchFormat) and
1248
self.__dict__ == other.__dict__)
1250
def get_format_description(self):
1251
return 'Remote BZR Branch'
1253
def get_format_string(self):
1254
return 'Remote BZR Branch'
1256
def open(self, a_bzrdir):
1257
return a_bzrdir.open_branch()
1259
def initialize(self, a_bzrdir):
1260
return a_bzrdir.create_branch()
1262
def supports_tags(self):
1263
# Remote branches might support tags, but we won't know until we
1264
# access the real remote branch.
1268
class RemoteBranch(branch.Branch, _RpcHelper):
1269
"""Branch stored on a server accessed by HPSS RPC.
1271
At the moment most operations are mapped down to simple file operations.
1274
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1276
"""Create a RemoteBranch instance.
1278
:param real_branch: An optional local implementation of the branch
1279
format, usually accessing the data via the VFS.
1280
:param _client: Private parameter for testing.
1282
# We intentionally don't call the parent class's __init__, because it
1283
# will try to assign to self.tags, which is a property in this subclass.
1284
# And the parent's __init__ doesn't do much anyway.
1285
self._revision_id_to_revno_cache = None
1286
self._revision_history_cache = None
1287
self._last_revision_info_cache = None
1288
self.bzrdir = remote_bzrdir
1289
if _client is not None:
1290
self._client = _client
1292
self._client = remote_bzrdir._client
1293
self.repository = remote_repository
1294
if real_branch is not None:
1295
self._real_branch = real_branch
1296
# Give the remote repository the matching real repo.
1297
real_repo = self._real_branch.repository
1298
if isinstance(real_repo, RemoteRepository):
1299
real_repo._ensure_real()
1300
real_repo = real_repo._real_repository
1301
self.repository._set_real_repository(real_repo)
1302
# Give the branch the remote repository to let fast-pathing happen.
1303
self._real_branch.repository = self.repository
1305
self._real_branch = None
1306
# Fill out expected attributes of branch for bzrlib api users.
1307
self._format = RemoteBranchFormat()
1308
self.base = self.bzrdir.root_transport.base
1309
self._control_files = None
1310
self._lock_mode = None
1311
self._lock_token = None
1312
self._repo_lock_token = None
1313
self._lock_count = 0
1314
self._leave_lock = False
1315
# The base class init is not called, so we duplicate this:
1316
hooks = branch.Branch.hooks['open']
1319
self._setup_stacking()
1321
def _setup_stacking(self):
1322
# configure stacking into the remote repository, by reading it from
1325
fallback_url = self.get_stacked_on_url()
1326
except (errors.NotStacked, errors.UnstackableBranchFormat,
1327
errors.UnstackableRepositoryFormat), e:
1329
# it's relative to this branch...
1330
fallback_url = urlutils.join(self.base, fallback_url)
1331
transports = [self.bzrdir.root_transport]
1332
if self._real_branch is not None:
1333
transports.append(self._real_branch._transport)
1334
stacked_on = branch.Branch.open(fallback_url,
1335
possible_transports=transports)
1336
self.repository.add_fallback_repository(stacked_on.repository)
1338
def _get_real_transport(self):
1339
# if we try vfs access, return the real branch's vfs transport
1341
return self._real_branch._transport
1343
_transport = property(_get_real_transport)
1346
return "%s(%s)" % (self.__class__.__name__, self.base)
1350
def _ensure_real(self):
1351
"""Ensure that there is a _real_branch set.
1353
Used before calls to self._real_branch.
1355
if self._real_branch is None:
1356
if not vfs.vfs_enabled():
1357
raise AssertionError('smart server vfs must be enabled '
1358
'to use vfs implementation')
1359
self.bzrdir._ensure_real()
1360
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1361
if self.repository._real_repository is None:
1362
# Give the remote repository the matching real repo.
1363
real_repo = self._real_branch.repository
1364
if isinstance(real_repo, RemoteRepository):
1365
real_repo._ensure_real()
1366
real_repo = real_repo._real_repository
1367
self.repository._set_real_repository(real_repo)
1368
# Give the real branch the remote repository to let fast-pathing
1370
self._real_branch.repository = self.repository
1371
if self._lock_mode == 'r':
1372
self._real_branch.lock_read()
1373
elif self._lock_mode == 'w':
1374
self._real_branch.lock_write(token=self._lock_token)
1376
def _translate_error(self, err, **context):
1377
self.repository._translate_error(err, branch=self, **context)
1379
def _clear_cached_state(self):
1380
super(RemoteBranch, self)._clear_cached_state()
1381
if self._real_branch is not None:
1382
self._real_branch._clear_cached_state()
1384
def _clear_cached_state_of_remote_branch_only(self):
1385
"""Like _clear_cached_state, but doesn't clear the cache of
1388
This is useful when falling back to calling a method of
1389
self._real_branch that changes state. In that case the underlying
1390
branch changes, so we need to invalidate this RemoteBranch's cache of
1391
it. However, there's no need to invalidate the _real_branch's cache
1392
too, in fact doing so might harm performance.
1394
super(RemoteBranch, self)._clear_cached_state()
1397
def control_files(self):
1398
# Defer actually creating RemoteBranchLockableFiles until its needed,
1399
# because it triggers an _ensure_real that we otherwise might not need.
1400
if self._control_files is None:
1401
self._control_files = RemoteBranchLockableFiles(
1402
self.bzrdir, self._client)
1403
return self._control_files
1405
def _get_checkout_format(self):
1407
return self._real_branch._get_checkout_format()
1409
def get_physical_lock_status(self):
1410
"""See Branch.get_physical_lock_status()."""
1411
# should be an API call to the server, as branches must be lockable.
1413
return self._real_branch.get_physical_lock_status()
1415
def get_stacked_on_url(self):
1416
"""Get the URL this branch is stacked against.
1418
:raises NotStacked: If the branch is not stacked.
1419
:raises UnstackableBranchFormat: If the branch does not support
1421
:raises UnstackableRepositoryFormat: If the repository does not support
1425
# there may not be a repository yet, so we can't use
1426
# self._translate_error, so we can't use self._call either.
1427
response = self._client.call('Branch.get_stacked_on_url',
1428
self._remote_path())
1429
except errors.ErrorFromSmartServer, err:
1430
# there may not be a repository yet, so we can't call through
1431
# its _translate_error
1432
_translate_error(err, branch=self)
1433
except errors.UnknownSmartMethod, err:
1435
return self._real_branch.get_stacked_on_url()
1436
if response[0] != 'ok':
1437
raise errors.UnexpectedSmartServerResponse(response)
1440
def lock_read(self):
1441
self.repository.lock_read()
1442
if not self._lock_mode:
1443
self._lock_mode = 'r'
1444
self._lock_count = 1
1445
if self._real_branch is not None:
1446
self._real_branch.lock_read()
1448
self._lock_count += 1
1450
def _remote_lock_write(self, token):
1452
branch_token = repo_token = ''
1454
branch_token = token
1455
repo_token = self.repository.lock_write()
1456
self.repository.unlock()
1457
err_context = {'token': token}
1458
response = self._call(
1459
'Branch.lock_write', self._remote_path(), branch_token,
1460
repo_token or '', **err_context)
1461
if response[0] != 'ok':
1462
raise errors.UnexpectedSmartServerResponse(response)
1463
ok, branch_token, repo_token = response
1464
return branch_token, repo_token
1466
def lock_write(self, token=None):
1467
if not self._lock_mode:
1468
# Lock the branch and repo in one remote call.
1469
remote_tokens = self._remote_lock_write(token)
1470
self._lock_token, self._repo_lock_token = remote_tokens
1471
if not self._lock_token:
1472
raise SmartProtocolError('Remote server did not return a token!')
1473
# Tell the self.repository object that it is locked.
1474
self.repository.lock_write(
1475
self._repo_lock_token, _skip_rpc=True)
1477
if self._real_branch is not None:
1478
self._real_branch.lock_write(token=self._lock_token)
1479
if token is not None:
1480
self._leave_lock = True
1482
self._leave_lock = False
1483
self._lock_mode = 'w'
1484
self._lock_count = 1
1485
elif self._lock_mode == 'r':
1486
raise errors.ReadOnlyTransaction
1488
if token is not None:
1489
# A token was given to lock_write, and we're relocking, so
1490
# check that the given token actually matches the one we
1492
if token != self._lock_token:
1493
raise errors.TokenMismatch(token, self._lock_token)
1494
self._lock_count += 1
1495
# Re-lock the repository too.
1496
self.repository.lock_write(self._repo_lock_token)
1497
return self._lock_token or None
1499
def _unlock(self, branch_token, repo_token):
1500
err_context = {'token': str((branch_token, repo_token))}
1501
response = self._call(
1502
'Branch.unlock', self._remote_path(), branch_token,
1503
repo_token or '', **err_context)
1504
if response == ('ok',):
1506
raise errors.UnexpectedSmartServerResponse(response)
1510
self._lock_count -= 1
1511
if not self._lock_count:
1512
self._clear_cached_state()
1513
mode = self._lock_mode
1514
self._lock_mode = None
1515
if self._real_branch is not None:
1516
if (not self._leave_lock and mode == 'w' and
1517
self._repo_lock_token):
1518
# If this RemoteBranch will remove the physical lock
1519
# for the repository, make sure the _real_branch
1520
# doesn't do it first. (Because the _real_branch's
1521
# repository is set to be the RemoteRepository.)
1522
self._real_branch.repository.leave_lock_in_place()
1523
self._real_branch.unlock()
1525
# Only write-locked branched need to make a remote method
1526
# call to perfom the unlock.
1528
if not self._lock_token:
1529
raise AssertionError('Locked, but no token!')
1530
branch_token = self._lock_token
1531
repo_token = self._repo_lock_token
1532
self._lock_token = None
1533
self._repo_lock_token = None
1534
if not self._leave_lock:
1535
self._unlock(branch_token, repo_token)
1537
self.repository.unlock()
1539
def break_lock(self):
1541
return self._real_branch.break_lock()
1543
def leave_lock_in_place(self):
1544
if not self._lock_token:
1545
raise NotImplementedError(self.leave_lock_in_place)
1546
self._leave_lock = True
1548
def dont_leave_lock_in_place(self):
1549
if not self._lock_token:
1550
raise NotImplementedError(self.dont_leave_lock_in_place)
1551
self._leave_lock = False
1553
def _last_revision_info(self):
1554
response = self._call('Branch.last_revision_info', self._remote_path())
1555
if response[0] != 'ok':
1556
raise SmartProtocolError('unexpected response code %s' % (response,))
1557
revno = int(response[1])
1558
last_revision = response[2]
1559
return (revno, last_revision)
1561
def _gen_revision_history(self):
1562
"""See Branch._gen_revision_history()."""
1563
response_tuple, response_handler = self._call_expecting_body(
1564
'Branch.revision_history', self._remote_path())
1565
if response_tuple[0] != 'ok':
1566
raise errors.UnexpectedSmartServerResponse(response_tuple)
1567
result = response_handler.read_body_bytes().split('\x00')
1572
def _remote_path(self):
1573
return self.bzrdir._path_for_remote_call(self._client)
1575
def _set_last_revision_descendant(self, revision_id, other_branch,
1576
allow_diverged=False, allow_overwrite_descendant=False):
1577
err_context = {'other_branch': other_branch}
1578
response = self._call('Branch.set_last_revision_ex',
1579
self._remote_path(), self._lock_token, self._repo_lock_token,
1580
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1582
self._clear_cached_state()
1583
if len(response) != 3 and response[0] != 'ok':
1584
raise errors.UnexpectedSmartServerResponse(response)
1585
new_revno, new_revision_id = response[1:]
1586
self._last_revision_info_cache = new_revno, new_revision_id
1587
if self._real_branch is not None:
1588
cache = new_revno, new_revision_id
1589
self._real_branch._last_revision_info_cache = cache
1591
def _set_last_revision(self, revision_id):
1592
self._clear_cached_state()
1593
response = self._call('Branch.set_last_revision',
1594
self._remote_path(), self._lock_token, self._repo_lock_token,
1596
if response != ('ok',):
1597
raise errors.UnexpectedSmartServerResponse(response)
1600
def set_revision_history(self, rev_history):
1601
# Send just the tip revision of the history; the server will generate
1602
# the full history from that. If the revision doesn't exist in this
1603
# branch, NoSuchRevision will be raised.
1604
if rev_history == []:
1607
rev_id = rev_history[-1]
1608
self._set_last_revision(rev_id)
1609
self._cache_revision_history(rev_history)
1611
def get_parent(self):
1613
return self._real_branch.get_parent()
1615
def set_parent(self, url):
1617
return self._real_branch.set_parent(url)
1619
def set_stacked_on_url(self, stacked_location):
1620
"""Set the URL this branch is stacked against.
1622
:raises UnstackableBranchFormat: If the branch does not support
1624
:raises UnstackableRepositoryFormat: If the repository does not support
1628
return self._real_branch.set_stacked_on_url(stacked_location)
1630
def sprout(self, to_bzrdir, revision_id=None):
1631
branch_format = to_bzrdir._format._branch_format
1632
if (branch_format is None or
1633
isinstance(branch_format, RemoteBranchFormat)):
1634
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1635
# implies the same thing), but RemoteBranches can't be created at
1636
# arbitrary URLs. So create a branch in the same format as
1637
# _real_branch instead.
1638
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1639
# to_bzrdir.create_branch to create a RemoteBranch after all...
1641
result = self._real_branch._format.initialize(to_bzrdir)
1642
self.copy_content_into(result, revision_id=revision_id)
1643
result.set_parent(self.bzrdir.root_transport.base)
1645
result = branch.Branch.sprout(
1646
self, to_bzrdir, revision_id=revision_id)
1650
def pull(self, source, overwrite=False, stop_revision=None,
1652
self._clear_cached_state_of_remote_branch_only()
1654
return self._real_branch.pull(
1655
source, overwrite=overwrite, stop_revision=stop_revision,
1656
_override_hook_target=self, **kwargs)
1659
def push(self, target, overwrite=False, stop_revision=None):
1661
return self._real_branch.push(
1662
target, overwrite=overwrite, stop_revision=stop_revision,
1663
_override_hook_source_branch=self)
1665
def is_locked(self):
1666
return self._lock_count >= 1
1669
def revision_id_to_revno(self, revision_id):
1671
return self._real_branch.revision_id_to_revno(revision_id)
1674
def set_last_revision_info(self, revno, revision_id):
1675
revision_id = ensure_null(revision_id)
1677
response = self._call('Branch.set_last_revision_info',
1678
self._remote_path(), self._lock_token, self._repo_lock_token,
1679
str(revno), revision_id)
1680
except errors.UnknownSmartMethod:
1682
self._clear_cached_state_of_remote_branch_only()
1683
self._real_branch.set_last_revision_info(revno, revision_id)
1684
self._last_revision_info_cache = revno, revision_id
1686
if response == ('ok',):
1687
self._clear_cached_state()
1688
self._last_revision_info_cache = revno, revision_id
1689
# Update the _real_branch's cache too.
1690
if self._real_branch is not None:
1691
cache = self._last_revision_info_cache
1692
self._real_branch._last_revision_info_cache = cache
1694
raise errors.UnexpectedSmartServerResponse(response)
1697
def generate_revision_history(self, revision_id, last_rev=None,
1699
medium = self._client._medium
1700
if not medium._is_remote_before((1, 6)):
1702
self._set_last_revision_descendant(revision_id, other_branch,
1703
allow_diverged=True, allow_overwrite_descendant=True)
1705
except errors.UnknownSmartMethod:
1706
medium._remember_remote_is_before((1, 6))
1707
self._clear_cached_state_of_remote_branch_only()
1709
self._real_branch.generate_revision_history(
1710
revision_id, last_rev=last_rev, other_branch=other_branch)
1715
return self._real_branch.tags
1717
def set_push_location(self, location):
1719
return self._real_branch.set_push_location(location)
1722
def update_revisions(self, other, stop_revision=None, overwrite=False,
1724
"""See Branch.update_revisions."""
1727
if stop_revision is None:
1728
stop_revision = other.last_revision()
1729
if revision.is_null(stop_revision):
1730
# if there are no commits, we're done.
1732
self.fetch(other, stop_revision)
1735
# Just unconditionally set the new revision. We don't care if
1736
# the branches have diverged.
1737
self._set_last_revision(stop_revision)
1739
medium = self._client._medium
1740
if not medium._is_remote_before((1, 6)):
1742
self._set_last_revision_descendant(stop_revision, other)
1744
except errors.UnknownSmartMethod:
1745
medium._remember_remote_is_before((1, 6))
1746
# Fallback for pre-1.6 servers: check for divergence
1747
# client-side, then do _set_last_revision.
1748
last_rev = revision.ensure_null(self.last_revision())
1750
graph = self.repository.get_graph()
1751
if self._check_if_descendant_or_diverged(
1752
stop_revision, last_rev, graph, other):
1753
# stop_revision is a descendant of last_rev, but we aren't
1754
# overwriting, so we're done.
1756
self._set_last_revision(stop_revision)
1761
def _extract_tar(tar, to_dir):
1762
"""Extract all the contents of a tarfile object.
1764
A replacement for extractall, which is not present in python2.4
1767
tar.extract(tarinfo, to_dir)
1770
def _translate_error(err, **context):
1771
"""Translate an ErrorFromSmartServer into a more useful error.
1773
Possible context keys:
1781
If the error from the server doesn't match a known pattern, then
1782
UnknownErrorFromSmartServer is raised.
1786
return context[name]
1787
except KeyError, key_err:
1788
mutter('Missing key %r in context %r', key_err.args[0], context)
1791
"""Get the path from the context if present, otherwise use first error
1795
return context['path']
1796
except KeyError, key_err:
1798
return err.error_args[0]
1799
except IndexError, idx_err:
1801
'Missing key %r in context %r', key_err.args[0], context)
1804
if err.error_verb == 'NoSuchRevision':
1805
raise NoSuchRevision(find('branch'), err.error_args[0])
1806
elif err.error_verb == 'nosuchrevision':
1807
raise NoSuchRevision(find('repository'), err.error_args[0])
1808
elif err.error_tuple == ('nobranch',):
1809
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1810
elif err.error_verb == 'norepository':
1811
raise errors.NoRepositoryPresent(find('bzrdir'))
1812
elif err.error_verb == 'LockContention':
1813
raise errors.LockContention('(remote lock)')
1814
elif err.error_verb == 'UnlockableTransport':
1815
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1816
elif err.error_verb == 'LockFailed':
1817
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1818
elif err.error_verb == 'TokenMismatch':
1819
raise errors.TokenMismatch(find('token'), '(remote token)')
1820
elif err.error_verb == 'Diverged':
1821
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1822
elif err.error_verb == 'TipChangeRejected':
1823
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1824
elif err.error_verb == 'UnstackableBranchFormat':
1825
raise errors.UnstackableBranchFormat(*err.error_args)
1826
elif err.error_verb == 'UnstackableRepositoryFormat':
1827
raise errors.UnstackableRepositoryFormat(*err.error_args)
1828
elif err.error_verb == 'NotStacked':
1829
raise errors.NotStacked(branch=find('branch'))
1830
elif err.error_verb == 'PermissionDenied':
1832
if len(err.error_args) >= 2:
1833
extra = err.error_args[1]
1836
raise errors.PermissionDenied(path, extra=extra)
1837
elif err.error_verb == 'ReadError':
1839
raise errors.ReadError(path)
1840
elif err.error_verb == 'NoSuchFile':
1842
raise errors.NoSuchFile(path)
1843
elif err.error_verb == 'FileExists':
1844
raise errors.FileExists(err.error_args[0])
1845
elif err.error_verb == 'DirectoryNotEmpty':
1846
raise errors.DirectoryNotEmpty(err.error_args[0])
1847
elif err.error_verb == 'ShortReadvError':
1848
args = err.error_args
1849
raise errors.ShortReadvError(
1850
args[0], int(args[1]), int(args[2]), int(args[3]))
1851
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1852
encoding = str(err.error_args[0]) # encoding must always be a string
1853
val = err.error_args[1]
1854
start = int(err.error_args[2])
1855
end = int(err.error_args[3])
1856
reason = str(err.error_args[4]) # reason must always be a string
1857
if val.startswith('u:'):
1858
val = val[2:].decode('utf-8')
1859
elif val.startswith('s:'):
1860
val = val[2:].decode('base64')
1861
if err.error_verb == 'UnicodeDecodeError':
1862
raise UnicodeDecodeError(encoding, val, start, end, reason)
1863
elif err.error_verb == 'UnicodeEncodeError':
1864
raise UnicodeEncodeError(encoding, val, start, end, reason)
1865
elif err.error_verb == 'ReadOnlyError':
1866
raise errors.TransportNotPossible('readonly transport')
1867
raise errors.UnknownErrorFromSmartServer(err)