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
self._unstacked_provider = graph.CachingParentsProvider(
323
get_parent_map=self._get_parent_map_rpc)
324
self._unstacked_provider.disable_cache()
326
# These depend on the actual remote format, so force them off for
327
# maximum compatibility. XXX: In future these should depend on the
328
# remote repository instance, but this is irrelevant until we perform
329
# reconcile via an RPC call.
330
self._reconcile_does_inventory_gc = False
331
self._reconcile_fixes_text_parents = False
332
self._reconcile_backsup_inventory = False
333
self.base = self.bzrdir.transport.base
334
# Additional places to query for data.
335
self._fallback_repositories = []
338
return "%s(%s)" % (self.__class__.__name__, self.base)
342
def abort_write_group(self, suppress_errors=False):
343
"""Complete a write group on the decorated repository.
345
Smart methods peform operations in a single step so this api
346
is not really applicable except as a compatibility thunk
347
for older plugins that don't use e.g. the CommitBuilder
350
:param suppress_errors: see Repository.abort_write_group.
353
return self._real_repository.abort_write_group(
354
suppress_errors=suppress_errors)
356
def commit_write_group(self):
357
"""Complete a write group on the decorated repository.
359
Smart methods peform operations in a single step so this api
360
is not really applicable except as a compatibility thunk
361
for older plugins that don't use e.g. the CommitBuilder
365
return self._real_repository.commit_write_group()
367
def _ensure_real(self):
368
"""Ensure that there is a _real_repository set.
370
Used before calls to self._real_repository.
372
if self._real_repository is None:
373
self.bzrdir._ensure_real()
374
self._set_real_repository(
375
self.bzrdir._real_bzrdir.open_repository())
377
def _translate_error(self, err, **context):
378
self.bzrdir._translate_error(err, repository=self, **context)
380
def find_text_key_references(self):
381
"""Find the text key references within the repository.
383
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
384
revision_ids. Each altered file-ids has the exact revision_ids that
385
altered it listed explicitly.
386
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
387
to whether they were referred to by the inventory of the
388
revision_id that they contain. The inventory texts from all present
389
revision ids are assessed to generate this report.
392
return self._real_repository.find_text_key_references()
394
def _generate_text_key_index(self):
395
"""Generate a new text key index for the repository.
397
This is an expensive function that will take considerable time to run.
399
:return: A dict mapping (file_id, revision_id) tuples to a list of
400
parents, also (file_id, revision_id) tuples.
403
return self._real_repository._generate_text_key_index()
405
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
406
def get_revision_graph(self, revision_id=None):
407
"""See Repository.get_revision_graph()."""
408
return self._get_revision_graph(revision_id)
410
def _get_revision_graph(self, revision_id):
411
"""Private method for using with old (< 1.2) servers to fallback."""
412
if revision_id is None:
414
elif revision.is_null(revision_id):
417
path = self.bzrdir._path_for_remote_call(self._client)
418
response = self._call_expecting_body(
419
'Repository.get_revision_graph', path, revision_id)
420
response_tuple, response_handler = response
421
if response_tuple[0] != 'ok':
422
raise errors.UnexpectedSmartServerResponse(response_tuple)
423
coded = response_handler.read_body_bytes()
425
# no revisions in this repository!
427
lines = coded.split('\n')
430
d = tuple(line.split())
431
revision_graph[d[0]] = d[1:]
433
return revision_graph
435
def has_revision(self, revision_id):
436
"""See Repository.has_revision()."""
437
if revision_id == NULL_REVISION:
438
# The null revision is always present.
440
path = self.bzrdir._path_for_remote_call(self._client)
441
response = self._call('Repository.has_revision', path, revision_id)
442
if response[0] not in ('yes', 'no'):
443
raise errors.UnexpectedSmartServerResponse(response)
444
if response[0] == 'yes':
446
for fallback_repo in self._fallback_repositories:
447
if fallback_repo.has_revision(revision_id):
451
def has_revisions(self, revision_ids):
452
"""See Repository.has_revisions()."""
453
# FIXME: This does many roundtrips, particularly when there are
454
# fallback repositories. -- mbp 20080905
456
for revision_id in revision_ids:
457
if self.has_revision(revision_id):
458
result.add(revision_id)
461
def has_same_location(self, other):
462
return (self.__class__ == other.__class__ and
463
self.bzrdir.transport.base == other.bzrdir.transport.base)
465
def get_graph(self, other_repository=None):
466
"""Return the graph for this repository format"""
467
parents_provider = self._make_parents_provider(other_repository)
468
return graph.Graph(parents_provider)
470
def gather_stats(self, revid=None, committers=None):
471
"""See Repository.gather_stats()."""
472
path = self.bzrdir._path_for_remote_call(self._client)
473
# revid can be None to indicate no revisions, not just NULL_REVISION
474
if revid is None or revision.is_null(revid):
478
if committers is None or not committers:
479
fmt_committers = 'no'
481
fmt_committers = 'yes'
482
response_tuple, response_handler = self._call_expecting_body(
483
'Repository.gather_stats', path, fmt_revid, fmt_committers)
484
if response_tuple[0] != 'ok':
485
raise errors.UnexpectedSmartServerResponse(response_tuple)
487
body = response_handler.read_body_bytes()
489
for line in body.split('\n'):
492
key, val_text = line.split(':')
493
if key in ('revisions', 'size', 'committers'):
494
result[key] = int(val_text)
495
elif key in ('firstrev', 'latestrev'):
496
values = val_text.split(' ')[1:]
497
result[key] = (float(values[0]), long(values[1]))
501
def find_branches(self, using=False):
502
"""See Repository.find_branches()."""
503
# should be an API call to the server.
505
return self._real_repository.find_branches(using=using)
507
def get_physical_lock_status(self):
508
"""See Repository.get_physical_lock_status()."""
509
# should be an API call to the server.
511
return self._real_repository.get_physical_lock_status()
513
def is_in_write_group(self):
514
"""Return True if there is an open write group.
516
write groups are only applicable locally for the smart server..
518
if self._real_repository:
519
return self._real_repository.is_in_write_group()
522
return self._lock_count >= 1
525
"""See Repository.is_shared()."""
526
path = self.bzrdir._path_for_remote_call(self._client)
527
response = self._call('Repository.is_shared', path)
528
if response[0] not in ('yes', 'no'):
529
raise SmartProtocolError('unexpected response code %s' % (response,))
530
return response[0] == 'yes'
532
def is_write_locked(self):
533
return self._lock_mode == 'w'
536
# wrong eventually - want a local lock cache context
537
if not self._lock_mode:
538
self._lock_mode = 'r'
540
self._unstacked_provider.enable_cache(cache_misses=False)
541
if self._real_repository is not None:
542
self._real_repository.lock_read()
544
self._lock_count += 1
546
def _remote_lock_write(self, token):
547
path = self.bzrdir._path_for_remote_call(self._client)
550
err_context = {'token': token}
551
response = self._call('Repository.lock_write', path, token,
553
if response[0] == 'ok':
557
raise errors.UnexpectedSmartServerResponse(response)
559
def lock_write(self, token=None, _skip_rpc=False):
560
if not self._lock_mode:
562
if self._lock_token is not None:
563
if token != self._lock_token:
564
raise errors.TokenMismatch(token, self._lock_token)
565
self._lock_token = token
567
self._lock_token = self._remote_lock_write(token)
568
# if self._lock_token is None, then this is something like packs or
569
# svn where we don't get to lock the repo, or a weave style repository
570
# where we cannot lock it over the wire and attempts to do so will
572
if self._real_repository is not None:
573
self._real_repository.lock_write(token=self._lock_token)
574
if token is not None:
575
self._leave_lock = True
577
self._leave_lock = False
578
self._lock_mode = 'w'
580
self._unstacked_provider.enable_cache(cache_misses=False)
581
elif self._lock_mode == 'r':
582
raise errors.ReadOnlyError(self)
584
self._lock_count += 1
585
return self._lock_token or None
587
def leave_lock_in_place(self):
588
if not self._lock_token:
589
raise NotImplementedError(self.leave_lock_in_place)
590
self._leave_lock = True
592
def dont_leave_lock_in_place(self):
593
if not self._lock_token:
594
raise NotImplementedError(self.dont_leave_lock_in_place)
595
self._leave_lock = False
597
def _set_real_repository(self, repository):
598
"""Set the _real_repository for this repository.
600
:param repository: The repository to fallback to for non-hpss
601
implemented operations.
603
if self._real_repository is not None:
604
raise AssertionError('_real_repository is already set')
605
if isinstance(repository, RemoteRepository):
606
raise AssertionError()
607
self._real_repository = repository
608
for fb in self._fallback_repositories:
609
self._real_repository.add_fallback_repository(fb)
610
if self._lock_mode == 'w':
611
# if we are already locked, the real repository must be able to
612
# acquire the lock with our token.
613
self._real_repository.lock_write(self._lock_token)
614
elif self._lock_mode == 'r':
615
self._real_repository.lock_read()
617
def start_write_group(self):
618
"""Start a write group on the decorated repository.
620
Smart methods peform operations in a single step so this api
621
is not really applicable except as a compatibility thunk
622
for older plugins that don't use e.g. the CommitBuilder
626
return self._real_repository.start_write_group()
628
def _unlock(self, token):
629
path = self.bzrdir._path_for_remote_call(self._client)
631
# with no token the remote repository is not persistently locked.
633
err_context = {'token': token}
634
response = self._call('Repository.unlock', path, token,
636
if response == ('ok',):
639
raise errors.UnexpectedSmartServerResponse(response)
642
self._lock_count -= 1
643
if self._lock_count > 0:
645
self._unstacked_provider.disable_cache()
646
old_mode = self._lock_mode
647
self._lock_mode = None
649
# The real repository is responsible at present for raising an
650
# exception if it's in an unfinished write group. However, it
651
# normally will *not* actually remove the lock from disk - that's
652
# done by the server on receiving the Repository.unlock call.
653
# This is just to let the _real_repository stay up to date.
654
if self._real_repository is not None:
655
self._real_repository.unlock()
657
# The rpc-level lock should be released even if there was a
658
# problem releasing the vfs-based lock.
660
# Only write-locked repositories need to make a remote method
661
# call to perfom the unlock.
662
old_token = self._lock_token
663
self._lock_token = None
664
if not self._leave_lock:
665
self._unlock(old_token)
667
def break_lock(self):
668
# should hand off to the network
670
return self._real_repository.break_lock()
672
def _get_tarball(self, compression):
673
"""Return a TemporaryFile containing a repository tarball.
675
Returns None if the server does not support sending tarballs.
678
path = self.bzrdir._path_for_remote_call(self._client)
680
response, protocol = self._call_expecting_body(
681
'Repository.tarball', path, compression)
682
except errors.UnknownSmartMethod:
683
protocol.cancel_read_body()
685
if response[0] == 'ok':
686
# Extract the tarball and return it
687
t = tempfile.NamedTemporaryFile()
688
# TODO: rpc layer should read directly into it...
689
t.write(protocol.read_body_bytes())
692
raise errors.UnexpectedSmartServerResponse(response)
694
def sprout(self, to_bzrdir, revision_id=None):
695
# TODO: Option to control what format is created?
697
dest_repo = self._real_repository._format.initialize(to_bzrdir,
699
dest_repo.fetch(self, revision_id=revision_id)
702
### These methods are just thin shims to the VFS object for now.
704
def revision_tree(self, revision_id):
706
return self._real_repository.revision_tree(revision_id)
708
def get_serializer_format(self):
710
return self._real_repository.get_serializer_format()
712
def get_commit_builder(self, branch, parents, config, timestamp=None,
713
timezone=None, committer=None, revprops=None,
715
# FIXME: It ought to be possible to call this without immediately
716
# triggering _ensure_real. For now it's the easiest thing to do.
718
real_repo = self._real_repository
719
builder = real_repo.get_commit_builder(branch, parents,
720
config, timestamp=timestamp, timezone=timezone,
721
committer=committer, revprops=revprops, revision_id=revision_id)
724
def add_fallback_repository(self, repository):
725
"""Add a repository to use for looking up data not held locally.
727
:param repository: A repository.
729
# XXX: At the moment the RemoteRepository will allow fallbacks
730
# unconditionally - however, a _real_repository will usually exist,
731
# and may raise an error if it's not accommodated by the underlying
732
# format. Eventually we should check when opening the repository
733
# whether it's willing to allow them or not.
735
# We need to accumulate additional repositories here, to pass them in
737
self._fallback_repositories.append(repository)
738
# They are also seen by the fallback repository. If it doesn't exist
739
# yet they'll be added then. This implicitly copies them.
742
def add_inventory(self, revid, inv, parents):
744
return self._real_repository.add_inventory(revid, inv, parents)
746
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
749
return self._real_repository.add_inventory_by_delta(basis_revision_id,
750
delta, new_revision_id, parents)
752
def add_revision(self, rev_id, rev, inv=None, config=None):
754
return self._real_repository.add_revision(
755
rev_id, rev, inv=inv, config=config)
758
def get_inventory(self, revision_id):
760
return self._real_repository.get_inventory(revision_id)
762
def iter_inventories(self, revision_ids):
764
return self._real_repository.iter_inventories(revision_ids)
767
def get_revision(self, revision_id):
769
return self._real_repository.get_revision(revision_id)
771
def get_transaction(self):
773
return self._real_repository.get_transaction()
776
def clone(self, a_bzrdir, revision_id=None):
778
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
780
def make_working_trees(self):
781
"""See Repository.make_working_trees"""
783
return self._real_repository.make_working_trees()
785
def revision_ids_to_search_result(self, result_set):
786
"""Convert a set of revision ids to a graph SearchResult."""
787
result_parents = set()
788
for parents in self.get_graph().get_parent_map(
789
result_set).itervalues():
790
result_parents.update(parents)
791
included_keys = result_set.intersection(result_parents)
792
start_keys = result_set.difference(included_keys)
793
exclude_keys = result_parents.difference(result_set)
794
result = graph.SearchResult(start_keys, exclude_keys,
795
len(result_set), result_set)
799
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
800
"""Return the revision ids that other has that this does not.
802
These are returned in topological order.
804
revision_id: only return revision ids included by revision_id.
806
return repository.InterRepository.get(
807
other, self).search_missing_revision_ids(revision_id, find_ghosts)
809
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
810
# Not delegated to _real_repository so that InterRepository.get has a
811
# chance to find an InterRepository specialised for RemoteRepository.
812
if self.has_same_location(source):
813
# check that last_revision is in 'from' and then return a
815
if (revision_id is not None and
816
not revision.is_null(revision_id)):
817
self.get_revision(revision_id)
819
inter = repository.InterRepository.get(source, self)
821
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
822
except NotImplementedError:
823
raise errors.IncompatibleRepositories(source, self)
825
def create_bundle(self, target, base, fileobj, format=None):
827
self._real_repository.create_bundle(target, base, fileobj, format)
830
def get_ancestry(self, revision_id, topo_sorted=True):
832
return self._real_repository.get_ancestry(revision_id, topo_sorted)
834
def fileids_altered_by_revision_ids(self, revision_ids):
836
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
838
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
840
return self._real_repository._get_versioned_file_checker(
841
revisions, revision_versions_cache)
843
def iter_files_bytes(self, desired_files):
844
"""See Repository.iter_file_bytes.
847
return self._real_repository.iter_files_bytes(desired_files)
850
def _fetch_order(self):
851
"""Decorate the real repository for now.
853
In the long term getting this back from the remote repository as part
854
of open would be more efficient.
857
return self._real_repository._fetch_order
860
def _fetch_uses_deltas(self):
861
"""Decorate the real repository for now.
863
In the long term getting this back from the remote repository as part
864
of open would be more efficient.
867
return self._real_repository._fetch_uses_deltas
870
def _fetch_reconcile(self):
871
"""Decorate the real repository for now.
873
In the long term getting this back from the remote repository as part
874
of open would be more efficient.
877
return self._real_repository._fetch_reconcile
879
def get_parent_map(self, revision_ids):
880
"""See bzrlib.Graph.get_parent_map()."""
881
return self._make_parents_provider().get_parent_map(revision_ids)
883
def _get_parent_map_rpc(self, keys):
884
"""Helper for get_parent_map that performs the RPC."""
885
medium = self._client._medium
886
if medium._is_remote_before((1, 2)):
887
# We already found out that the server can't understand
888
# Repository.get_parent_map requests, so just fetch the whole
890
# XXX: Note that this will issue a deprecation warning. This is ok
891
# :- its because we're working with a deprecated server anyway, and
892
# the user will almost certainly have seen a warning about the
893
# server version already.
894
rg = self.get_revision_graph()
895
# There is an api discrepency between get_parent_map and
896
# get_revision_graph. Specifically, a "key:()" pair in
897
# get_revision_graph just means a node has no parents. For
898
# "get_parent_map" it means the node is a ghost. So fix up the
899
# graph to correct this.
900
# https://bugs.launchpad.net/bzr/+bug/214894
901
# There is one other "bug" which is that ghosts in
902
# get_revision_graph() are not returned at all. But we won't worry
903
# about that for now.
904
for node_id, parent_ids in rg.iteritems():
906
rg[node_id] = (NULL_REVISION,)
907
rg[NULL_REVISION] = ()
912
raise ValueError('get_parent_map(None) is not valid')
913
if NULL_REVISION in keys:
914
keys.discard(NULL_REVISION)
915
found_parents = {NULL_REVISION:()}
920
# TODO(Needs analysis): We could assume that the keys being requested
921
# from get_parent_map are in a breadth first search, so typically they
922
# will all be depth N from some common parent, and we don't have to
923
# have the server iterate from the root parent, but rather from the
924
# keys we're searching; and just tell the server the keyspace we
925
# already have; but this may be more traffic again.
927
# Transform self._parents_map into a search request recipe.
928
# TODO: Manage this incrementally to avoid covering the same path
929
# repeatedly. (The server will have to on each request, but the less
930
# work done the better).
931
parents_map = self._unstacked_provider.get_cached_map()
932
if parents_map is None:
933
# Repository is not locked, so there's no cache.
935
start_set = set(parents_map)
936
result_parents = set()
937
for parents in parents_map.itervalues():
938
result_parents.update(parents)
939
stop_keys = result_parents.difference(start_set)
940
included_keys = start_set.intersection(result_parents)
941
start_set.difference_update(included_keys)
942
recipe = (start_set, stop_keys, len(parents_map))
943
body = self._serialise_search_recipe(recipe)
944
path = self.bzrdir._path_for_remote_call(self._client)
946
if type(key) is not str:
948
"key %r not a plain string" % (key,))
949
verb = 'Repository.get_parent_map'
950
args = (path,) + tuple(keys)
952
response = self._call_with_body_bytes_expecting_body(
954
except errors.UnknownSmartMethod:
955
# Server does not support this method, so get the whole graph.
956
# Worse, we have to force a disconnection, because the server now
957
# doesn't realise it has a body on the wire to consume, so the
958
# only way to recover is to abandon the connection.
960
'Server is too old for fast get_parent_map, reconnecting. '
961
'(Upgrade the server to Bazaar 1.2 to avoid this)')
963
# To avoid having to disconnect repeatedly, we keep track of the
964
# fact the server doesn't understand remote methods added in 1.2.
965
medium._remember_remote_is_before((1, 2))
966
return self.get_revision_graph(None)
967
response_tuple, response_handler = response
968
if response_tuple[0] not in ['ok']:
969
response_handler.cancel_read_body()
970
raise errors.UnexpectedSmartServerResponse(response_tuple)
971
if response_tuple[0] == 'ok':
972
coded = bz2.decompress(response_handler.read_body_bytes())
976
lines = coded.split('\n')
979
d = tuple(line.split())
981
revision_graph[d[0]] = d[1:]
983
# No parents - so give the Graph result (NULL_REVISION,).
984
revision_graph[d[0]] = (NULL_REVISION,)
985
return revision_graph
988
def get_signature_text(self, revision_id):
990
return self._real_repository.get_signature_text(revision_id)
993
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
994
def get_revision_graph_with_ghosts(self, revision_ids=None):
996
return self._real_repository.get_revision_graph_with_ghosts(
997
revision_ids=revision_ids)
1000
def get_inventory_xml(self, revision_id):
1002
return self._real_repository.get_inventory_xml(revision_id)
1004
def deserialise_inventory(self, revision_id, xml):
1006
return self._real_repository.deserialise_inventory(revision_id, xml)
1008
def reconcile(self, other=None, thorough=False):
1010
return self._real_repository.reconcile(other=other, thorough=thorough)
1012
def all_revision_ids(self):
1014
return self._real_repository.all_revision_ids()
1017
def get_deltas_for_revisions(self, revisions):
1019
return self._real_repository.get_deltas_for_revisions(revisions)
1022
def get_revision_delta(self, revision_id):
1024
return self._real_repository.get_revision_delta(revision_id)
1027
def revision_trees(self, revision_ids):
1029
return self._real_repository.revision_trees(revision_ids)
1032
def get_revision_reconcile(self, revision_id):
1034
return self._real_repository.get_revision_reconcile(revision_id)
1037
def check(self, revision_ids=None):
1039
return self._real_repository.check(revision_ids=revision_ids)
1041
def copy_content_into(self, destination, revision_id=None):
1043
return self._real_repository.copy_content_into(
1044
destination, revision_id=revision_id)
1046
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1047
# get a tarball of the remote repository, and copy from that into the
1049
from bzrlib import osutils
1051
# TODO: Maybe a progress bar while streaming the tarball?
1052
note("Copying repository content as tarball...")
1053
tar_file = self._get_tarball('bz2')
1054
if tar_file is None:
1056
destination = to_bzrdir.create_repository()
1058
tar = tarfile.open('repository', fileobj=tar_file,
1060
tmpdir = osutils.mkdtemp()
1062
_extract_tar(tar, tmpdir)
1063
tmp_bzrdir = BzrDir.open(tmpdir)
1064
tmp_repo = tmp_bzrdir.open_repository()
1065
tmp_repo.copy_content_into(destination, revision_id)
1067
osutils.rmtree(tmpdir)
1071
# TODO: Suggestion from john: using external tar is much faster than
1072
# python's tarfile library, but it may not work on windows.
1075
def inventories(self):
1076
"""Decorate the real repository for now.
1078
In the long term a full blown network facility is needed to
1079
avoid creating a real repository object locally.
1082
return self._real_repository.inventories
1086
"""Compress the data within the repository.
1088
This is not currently implemented within the smart server.
1091
return self._real_repository.pack()
1094
def revisions(self):
1095
"""Decorate the real repository for now.
1097
In the short term this should become a real object to intercept graph
1100
In the long term a full blown network facility is needed.
1103
return self._real_repository.revisions
1105
def set_make_working_trees(self, new_value):
1107
self._real_repository.set_make_working_trees(new_value)
1110
def signatures(self):
1111
"""Decorate the real repository for now.
1113
In the long term a full blown network facility is needed to avoid
1114
creating a real repository object locally.
1117
return self._real_repository.signatures
1120
def sign_revision(self, revision_id, gpg_strategy):
1122
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1126
"""Decorate the real repository for now.
1128
In the long term a full blown network facility is needed to avoid
1129
creating a real repository object locally.
1132
return self._real_repository.texts
1135
def get_revisions(self, revision_ids):
1137
return self._real_repository.get_revisions(revision_ids)
1139
def supports_rich_root(self):
1141
return self._real_repository.supports_rich_root()
1143
def iter_reverse_revision_history(self, revision_id):
1145
return self._real_repository.iter_reverse_revision_history(revision_id)
1148
def _serializer(self):
1150
return self._real_repository._serializer
1152
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1154
return self._real_repository.store_revision_signature(
1155
gpg_strategy, plaintext, revision_id)
1157
def add_signature_text(self, revision_id, signature):
1159
return self._real_repository.add_signature_text(revision_id, signature)
1161
def has_signature_for_revision_id(self, revision_id):
1163
return self._real_repository.has_signature_for_revision_id(revision_id)
1165
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1167
return self._real_repository.item_keys_introduced_by(revision_ids,
1168
_files_pb=_files_pb)
1170
def revision_graph_can_have_wrong_parents(self):
1171
# The answer depends on the remote repo format.
1173
return self._real_repository.revision_graph_can_have_wrong_parents()
1175
def _find_inconsistent_revision_parents(self):
1177
return self._real_repository._find_inconsistent_revision_parents()
1179
def _check_for_inconsistent_revision_parents(self):
1181
return self._real_repository._check_for_inconsistent_revision_parents()
1183
def _make_parents_provider(self, other=None):
1184
providers = [self._unstacked_provider]
1185
if other is not None:
1186
providers.insert(0, other)
1187
providers.extend(r._make_parents_provider() for r in
1188
self._fallback_repositories)
1189
return graph._StackedParentsProvider(providers)
1191
def _serialise_search_recipe(self, recipe):
1192
"""Serialise a graph search recipe.
1194
:param recipe: A search recipe (start, stop, count).
1195
:return: Serialised bytes.
1197
start_keys = ' '.join(recipe[0])
1198
stop_keys = ' '.join(recipe[1])
1199
count = str(recipe[2])
1200
return '\n'.join((start_keys, stop_keys, count))
1203
path = self.bzrdir._path_for_remote_call(self._client)
1205
response = self._call('PackRepository.autopack', path)
1206
except errors.UnknownSmartMethod:
1208
self._real_repository._pack_collection.autopack()
1210
if self._real_repository is not None:
1211
# Reset the real repository's cache of pack names.
1212
# XXX: At some point we may be able to skip this and just rely on
1213
# the automatic retry logic to do the right thing, but for now we
1214
# err on the side of being correct rather than being optimal.
1215
self._real_repository._pack_collection.reload_pack_names()
1216
if response[0] != 'ok':
1217
raise errors.UnexpectedSmartServerResponse(response)
1220
class RemoteBranchLockableFiles(LockableFiles):
1221
"""A 'LockableFiles' implementation that talks to a smart server.
1223
This is not a public interface class.
1226
def __init__(self, bzrdir, _client):
1227
self.bzrdir = bzrdir
1228
self._client = _client
1229
self._need_find_modes = True
1230
LockableFiles.__init__(
1231
self, bzrdir.get_branch_transport(None),
1232
'lock', lockdir.LockDir)
1234
def _find_modes(self):
1235
# RemoteBranches don't let the client set the mode of control files.
1236
self._dir_mode = None
1237
self._file_mode = None
1240
class RemoteBranchFormat(branch.BranchFormat):
1243
super(RemoteBranchFormat, self).__init__()
1244
self._matchingbzrdir = RemoteBzrDirFormat()
1245
self._matchingbzrdir.set_branch_format(self)
1247
def __eq__(self, other):
1248
return (isinstance(other, RemoteBranchFormat) and
1249
self.__dict__ == other.__dict__)
1251
def get_format_description(self):
1252
return 'Remote BZR Branch'
1254
def get_format_string(self):
1255
return 'Remote BZR Branch'
1257
def open(self, a_bzrdir):
1258
return a_bzrdir.open_branch()
1260
def initialize(self, a_bzrdir):
1261
return a_bzrdir.create_branch()
1263
def supports_tags(self):
1264
# Remote branches might support tags, but we won't know until we
1265
# access the real remote branch.
1269
class RemoteBranch(branch.Branch, _RpcHelper):
1270
"""Branch stored on a server accessed by HPSS RPC.
1272
At the moment most operations are mapped down to simple file operations.
1275
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1277
"""Create a RemoteBranch instance.
1279
:param real_branch: An optional local implementation of the branch
1280
format, usually accessing the data via the VFS.
1281
:param _client: Private parameter for testing.
1283
# We intentionally don't call the parent class's __init__, because it
1284
# will try to assign to self.tags, which is a property in this subclass.
1285
# And the parent's __init__ doesn't do much anyway.
1286
self._revision_id_to_revno_cache = None
1287
self._revision_history_cache = None
1288
self._last_revision_info_cache = None
1289
self.bzrdir = remote_bzrdir
1290
if _client is not None:
1291
self._client = _client
1293
self._client = remote_bzrdir._client
1294
self.repository = remote_repository
1295
if real_branch is not None:
1296
self._real_branch = real_branch
1297
# Give the remote repository the matching real repo.
1298
real_repo = self._real_branch.repository
1299
if isinstance(real_repo, RemoteRepository):
1300
real_repo._ensure_real()
1301
real_repo = real_repo._real_repository
1302
self.repository._set_real_repository(real_repo)
1303
# Give the branch the remote repository to let fast-pathing happen.
1304
self._real_branch.repository = self.repository
1306
self._real_branch = None
1307
# Fill out expected attributes of branch for bzrlib api users.
1308
self._format = RemoteBranchFormat()
1309
self.base = self.bzrdir.root_transport.base
1310
self._control_files = None
1311
self._lock_mode = None
1312
self._lock_token = None
1313
self._repo_lock_token = None
1314
self._lock_count = 0
1315
self._leave_lock = False
1316
# The base class init is not called, so we duplicate this:
1317
hooks = branch.Branch.hooks['open']
1320
self._setup_stacking()
1322
def _setup_stacking(self):
1323
# configure stacking into the remote repository, by reading it from
1326
fallback_url = self.get_stacked_on_url()
1327
except (errors.NotStacked, errors.UnstackableBranchFormat,
1328
errors.UnstackableRepositoryFormat), e:
1330
# it's relative to this branch...
1331
fallback_url = urlutils.join(self.base, fallback_url)
1332
transports = [self.bzrdir.root_transport]
1333
if self._real_branch is not None:
1334
transports.append(self._real_branch._transport)
1335
stacked_on = branch.Branch.open(fallback_url,
1336
possible_transports=transports)
1337
self.repository.add_fallback_repository(stacked_on.repository)
1339
def _get_real_transport(self):
1340
# if we try vfs access, return the real branch's vfs transport
1342
return self._real_branch._transport
1344
_transport = property(_get_real_transport)
1347
return "%s(%s)" % (self.__class__.__name__, self.base)
1351
def _ensure_real(self):
1352
"""Ensure that there is a _real_branch set.
1354
Used before calls to self._real_branch.
1356
if self._real_branch is None:
1357
if not vfs.vfs_enabled():
1358
raise AssertionError('smart server vfs must be enabled '
1359
'to use vfs implementation')
1360
self.bzrdir._ensure_real()
1361
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1362
if self.repository._real_repository is None:
1363
# Give the remote repository the matching real repo.
1364
real_repo = self._real_branch.repository
1365
if isinstance(real_repo, RemoteRepository):
1366
real_repo._ensure_real()
1367
real_repo = real_repo._real_repository
1368
self.repository._set_real_repository(real_repo)
1369
# Give the real branch the remote repository to let fast-pathing
1371
self._real_branch.repository = self.repository
1372
if self._lock_mode == 'r':
1373
self._real_branch.lock_read()
1374
elif self._lock_mode == 'w':
1375
self._real_branch.lock_write(token=self._lock_token)
1377
def _translate_error(self, err, **context):
1378
self.repository._translate_error(err, branch=self, **context)
1380
def _clear_cached_state(self):
1381
super(RemoteBranch, self)._clear_cached_state()
1382
if self._real_branch is not None:
1383
self._real_branch._clear_cached_state()
1385
def _clear_cached_state_of_remote_branch_only(self):
1386
"""Like _clear_cached_state, but doesn't clear the cache of
1389
This is useful when falling back to calling a method of
1390
self._real_branch that changes state. In that case the underlying
1391
branch changes, so we need to invalidate this RemoteBranch's cache of
1392
it. However, there's no need to invalidate the _real_branch's cache
1393
too, in fact doing so might harm performance.
1395
super(RemoteBranch, self)._clear_cached_state()
1398
def control_files(self):
1399
# Defer actually creating RemoteBranchLockableFiles until its needed,
1400
# because it triggers an _ensure_real that we otherwise might not need.
1401
if self._control_files is None:
1402
self._control_files = RemoteBranchLockableFiles(
1403
self.bzrdir, self._client)
1404
return self._control_files
1406
def _get_checkout_format(self):
1408
return self._real_branch._get_checkout_format()
1410
def get_physical_lock_status(self):
1411
"""See Branch.get_physical_lock_status()."""
1412
# should be an API call to the server, as branches must be lockable.
1414
return self._real_branch.get_physical_lock_status()
1416
def get_stacked_on_url(self):
1417
"""Get the URL this branch is stacked against.
1419
:raises NotStacked: If the branch is not stacked.
1420
:raises UnstackableBranchFormat: If the branch does not support
1422
:raises UnstackableRepositoryFormat: If the repository does not support
1426
# there may not be a repository yet, so we can't use
1427
# self._translate_error, so we can't use self._call either.
1428
response = self._client.call('Branch.get_stacked_on_url',
1429
self._remote_path())
1430
except errors.ErrorFromSmartServer, err:
1431
# there may not be a repository yet, so we can't call through
1432
# its _translate_error
1433
_translate_error(err, branch=self)
1434
except errors.UnknownSmartMethod, err:
1436
return self._real_branch.get_stacked_on_url()
1437
if response[0] != 'ok':
1438
raise errors.UnexpectedSmartServerResponse(response)
1441
def lock_read(self):
1442
self.repository.lock_read()
1443
if not self._lock_mode:
1444
self._lock_mode = 'r'
1445
self._lock_count = 1
1446
if self._real_branch is not None:
1447
self._real_branch.lock_read()
1449
self._lock_count += 1
1451
def _remote_lock_write(self, token):
1453
branch_token = repo_token = ''
1455
branch_token = token
1456
repo_token = self.repository.lock_write()
1457
self.repository.unlock()
1458
err_context = {'token': token}
1459
response = self._call(
1460
'Branch.lock_write', self._remote_path(), branch_token,
1461
repo_token or '', **err_context)
1462
if response[0] != 'ok':
1463
raise errors.UnexpectedSmartServerResponse(response)
1464
ok, branch_token, repo_token = response
1465
return branch_token, repo_token
1467
def lock_write(self, token=None):
1468
if not self._lock_mode:
1469
# Lock the branch and repo in one remote call.
1470
remote_tokens = self._remote_lock_write(token)
1471
self._lock_token, self._repo_lock_token = remote_tokens
1472
if not self._lock_token:
1473
raise SmartProtocolError('Remote server did not return a token!')
1474
# Tell the self.repository object that it is locked.
1475
self.repository.lock_write(
1476
self._repo_lock_token, _skip_rpc=True)
1478
if self._real_branch is not None:
1479
self._real_branch.lock_write(token=self._lock_token)
1480
if token is not None:
1481
self._leave_lock = True
1483
self._leave_lock = False
1484
self._lock_mode = 'w'
1485
self._lock_count = 1
1486
elif self._lock_mode == 'r':
1487
raise errors.ReadOnlyTransaction
1489
if token is not None:
1490
# A token was given to lock_write, and we're relocking, so
1491
# check that the given token actually matches the one we
1493
if token != self._lock_token:
1494
raise errors.TokenMismatch(token, self._lock_token)
1495
self._lock_count += 1
1496
# Re-lock the repository too.
1497
self.repository.lock_write(self._repo_lock_token)
1498
return self._lock_token or None
1500
def _unlock(self, branch_token, repo_token):
1501
err_context = {'token': str((branch_token, repo_token))}
1502
response = self._call(
1503
'Branch.unlock', self._remote_path(), branch_token,
1504
repo_token or '', **err_context)
1505
if response == ('ok',):
1507
raise errors.UnexpectedSmartServerResponse(response)
1511
self._lock_count -= 1
1512
if not self._lock_count:
1513
self._clear_cached_state()
1514
mode = self._lock_mode
1515
self._lock_mode = None
1516
if self._real_branch is not None:
1517
if (not self._leave_lock and mode == 'w' and
1518
self._repo_lock_token):
1519
# If this RemoteBranch will remove the physical lock
1520
# for the repository, make sure the _real_branch
1521
# doesn't do it first. (Because the _real_branch's
1522
# repository is set to be the RemoteRepository.)
1523
self._real_branch.repository.leave_lock_in_place()
1524
self._real_branch.unlock()
1526
# Only write-locked branched need to make a remote method
1527
# call to perfom the unlock.
1529
if not self._lock_token:
1530
raise AssertionError('Locked, but no token!')
1531
branch_token = self._lock_token
1532
repo_token = self._repo_lock_token
1533
self._lock_token = None
1534
self._repo_lock_token = None
1535
if not self._leave_lock:
1536
self._unlock(branch_token, repo_token)
1538
self.repository.unlock()
1540
def break_lock(self):
1542
return self._real_branch.break_lock()
1544
def leave_lock_in_place(self):
1545
if not self._lock_token:
1546
raise NotImplementedError(self.leave_lock_in_place)
1547
self._leave_lock = True
1549
def dont_leave_lock_in_place(self):
1550
if not self._lock_token:
1551
raise NotImplementedError(self.dont_leave_lock_in_place)
1552
self._leave_lock = False
1554
def _last_revision_info(self):
1555
response = self._call('Branch.last_revision_info', self._remote_path())
1556
if response[0] != 'ok':
1557
raise SmartProtocolError('unexpected response code %s' % (response,))
1558
revno = int(response[1])
1559
last_revision = response[2]
1560
return (revno, last_revision)
1562
def _gen_revision_history(self):
1563
"""See Branch._gen_revision_history()."""
1564
response_tuple, response_handler = self._call_expecting_body(
1565
'Branch.revision_history', self._remote_path())
1566
if response_tuple[0] != 'ok':
1567
raise errors.UnexpectedSmartServerResponse(response_tuple)
1568
result = response_handler.read_body_bytes().split('\x00')
1573
def _remote_path(self):
1574
return self.bzrdir._path_for_remote_call(self._client)
1576
def _set_last_revision_descendant(self, revision_id, other_branch,
1577
allow_diverged=False, allow_overwrite_descendant=False):
1578
err_context = {'other_branch': other_branch}
1579
response = self._call('Branch.set_last_revision_ex',
1580
self._remote_path(), self._lock_token, self._repo_lock_token,
1581
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1583
self._clear_cached_state()
1584
if len(response) != 3 and response[0] != 'ok':
1585
raise errors.UnexpectedSmartServerResponse(response)
1586
new_revno, new_revision_id = response[1:]
1587
self._last_revision_info_cache = new_revno, new_revision_id
1588
if self._real_branch is not None:
1589
cache = new_revno, new_revision_id
1590
self._real_branch._last_revision_info_cache = cache
1592
def _set_last_revision(self, revision_id):
1593
self._clear_cached_state()
1594
response = self._call('Branch.set_last_revision',
1595
self._remote_path(), self._lock_token, self._repo_lock_token,
1597
if response != ('ok',):
1598
raise errors.UnexpectedSmartServerResponse(response)
1601
def set_revision_history(self, rev_history):
1602
# Send just the tip revision of the history; the server will generate
1603
# the full history from that. If the revision doesn't exist in this
1604
# branch, NoSuchRevision will be raised.
1605
if rev_history == []:
1608
rev_id = rev_history[-1]
1609
self._set_last_revision(rev_id)
1610
self._cache_revision_history(rev_history)
1612
def get_parent(self):
1614
return self._real_branch.get_parent()
1616
def set_parent(self, url):
1618
return self._real_branch.set_parent(url)
1620
def set_stacked_on_url(self, stacked_location):
1621
"""Set the URL this branch is stacked against.
1623
:raises UnstackableBranchFormat: If the branch does not support
1625
:raises UnstackableRepositoryFormat: If the repository does not support
1629
return self._real_branch.set_stacked_on_url(stacked_location)
1631
def sprout(self, to_bzrdir, revision_id=None):
1632
branch_format = to_bzrdir._format._branch_format
1633
if (branch_format is None or
1634
isinstance(branch_format, RemoteBranchFormat)):
1635
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1636
# implies the same thing), but RemoteBranches can't be created at
1637
# arbitrary URLs. So create a branch in the same format as
1638
# _real_branch instead.
1639
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1640
# to_bzrdir.create_branch to create a RemoteBranch after all...
1642
result = self._real_branch._format.initialize(to_bzrdir)
1643
self.copy_content_into(result, revision_id=revision_id)
1644
result.set_parent(self.bzrdir.root_transport.base)
1646
result = branch.Branch.sprout(
1647
self, to_bzrdir, revision_id=revision_id)
1651
def pull(self, source, overwrite=False, stop_revision=None,
1653
self._clear_cached_state_of_remote_branch_only()
1655
return self._real_branch.pull(
1656
source, overwrite=overwrite, stop_revision=stop_revision,
1657
_override_hook_target=self, **kwargs)
1660
def push(self, target, overwrite=False, stop_revision=None):
1662
return self._real_branch.push(
1663
target, overwrite=overwrite, stop_revision=stop_revision,
1664
_override_hook_source_branch=self)
1666
def is_locked(self):
1667
return self._lock_count >= 1
1670
def revision_id_to_revno(self, revision_id):
1672
return self._real_branch.revision_id_to_revno(revision_id)
1675
def set_last_revision_info(self, revno, revision_id):
1676
revision_id = ensure_null(revision_id)
1678
response = self._call('Branch.set_last_revision_info',
1679
self._remote_path(), self._lock_token, self._repo_lock_token,
1680
str(revno), revision_id)
1681
except errors.UnknownSmartMethod:
1683
self._clear_cached_state_of_remote_branch_only()
1684
self._real_branch.set_last_revision_info(revno, revision_id)
1685
self._last_revision_info_cache = revno, revision_id
1687
if response == ('ok',):
1688
self._clear_cached_state()
1689
self._last_revision_info_cache = revno, revision_id
1690
# Update the _real_branch's cache too.
1691
if self._real_branch is not None:
1692
cache = self._last_revision_info_cache
1693
self._real_branch._last_revision_info_cache = cache
1695
raise errors.UnexpectedSmartServerResponse(response)
1698
def generate_revision_history(self, revision_id, last_rev=None,
1700
medium = self._client._medium
1701
if not medium._is_remote_before((1, 6)):
1703
self._set_last_revision_descendant(revision_id, other_branch,
1704
allow_diverged=True, allow_overwrite_descendant=True)
1706
except errors.UnknownSmartMethod:
1707
medium._remember_remote_is_before((1, 6))
1708
self._clear_cached_state_of_remote_branch_only()
1710
self._real_branch.generate_revision_history(
1711
revision_id, last_rev=last_rev, other_branch=other_branch)
1716
return self._real_branch.tags
1718
def set_push_location(self, location):
1720
return self._real_branch.set_push_location(location)
1723
def update_revisions(self, other, stop_revision=None, overwrite=False,
1725
"""See Branch.update_revisions."""
1728
if stop_revision is None:
1729
stop_revision = other.last_revision()
1730
if revision.is_null(stop_revision):
1731
# if there are no commits, we're done.
1733
self.fetch(other, stop_revision)
1736
# Just unconditionally set the new revision. We don't care if
1737
# the branches have diverged.
1738
self._set_last_revision(stop_revision)
1740
medium = self._client._medium
1741
if not medium._is_remote_before((1, 6)):
1743
self._set_last_revision_descendant(stop_revision, other)
1745
except errors.UnknownSmartMethod:
1746
medium._remember_remote_is_before((1, 6))
1747
# Fallback for pre-1.6 servers: check for divergence
1748
# client-side, then do _set_last_revision.
1749
last_rev = revision.ensure_null(self.last_revision())
1751
graph = self.repository.get_graph()
1752
if self._check_if_descendant_or_diverged(
1753
stop_revision, last_rev, graph, other):
1754
# stop_revision is a descendant of last_rev, but we aren't
1755
# overwriting, so we're done.
1757
self._set_last_revision(stop_revision)
1762
def _extract_tar(tar, to_dir):
1763
"""Extract all the contents of a tarfile object.
1765
A replacement for extractall, which is not present in python2.4
1768
tar.extract(tarinfo, to_dir)
1771
def _translate_error(err, **context):
1772
"""Translate an ErrorFromSmartServer into a more useful error.
1774
Possible context keys:
1782
If the error from the server doesn't match a known pattern, then
1783
UnknownErrorFromSmartServer is raised.
1787
return context[name]
1788
except KeyError, key_err:
1789
mutter('Missing key %r in context %r', key_err.args[0], context)
1792
"""Get the path from the context if present, otherwise use first error
1796
return context['path']
1797
except KeyError, key_err:
1799
return err.error_args[0]
1800
except IndexError, idx_err:
1802
'Missing key %r in context %r', key_err.args[0], context)
1805
if err.error_verb == 'NoSuchRevision':
1806
raise NoSuchRevision(find('branch'), err.error_args[0])
1807
elif err.error_verb == 'nosuchrevision':
1808
raise NoSuchRevision(find('repository'), err.error_args[0])
1809
elif err.error_tuple == ('nobranch',):
1810
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1811
elif err.error_verb == 'norepository':
1812
raise errors.NoRepositoryPresent(find('bzrdir'))
1813
elif err.error_verb == 'LockContention':
1814
raise errors.LockContention('(remote lock)')
1815
elif err.error_verb == 'UnlockableTransport':
1816
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1817
elif err.error_verb == 'LockFailed':
1818
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1819
elif err.error_verb == 'TokenMismatch':
1820
raise errors.TokenMismatch(find('token'), '(remote token)')
1821
elif err.error_verb == 'Diverged':
1822
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1823
elif err.error_verb == 'TipChangeRejected':
1824
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1825
elif err.error_verb == 'UnstackableBranchFormat':
1826
raise errors.UnstackableBranchFormat(*err.error_args)
1827
elif err.error_verb == 'UnstackableRepositoryFormat':
1828
raise errors.UnstackableRepositoryFormat(*err.error_args)
1829
elif err.error_verb == 'NotStacked':
1830
raise errors.NotStacked(branch=find('branch'))
1831
elif err.error_verb == 'PermissionDenied':
1833
if len(err.error_args) >= 2:
1834
extra = err.error_args[1]
1837
raise errors.PermissionDenied(path, extra=extra)
1838
elif err.error_verb == 'ReadError':
1840
raise errors.ReadError(path)
1841
elif err.error_verb == 'NoSuchFile':
1843
raise errors.NoSuchFile(path)
1844
elif err.error_verb == 'FileExists':
1845
raise errors.FileExists(err.error_args[0])
1846
elif err.error_verb == 'DirectoryNotEmpty':
1847
raise errors.DirectoryNotEmpty(err.error_args[0])
1848
elif err.error_verb == 'ShortReadvError':
1849
args = err.error_args
1850
raise errors.ShortReadvError(
1851
args[0], int(args[1]), int(args[2]), int(args[3]))
1852
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1853
encoding = str(err.error_args[0]) # encoding must always be a string
1854
val = err.error_args[1]
1855
start = int(err.error_args[2])
1856
end = int(err.error_args[3])
1857
reason = str(err.error_args[4]) # reason must always be a string
1858
if val.startswith('u:'):
1859
val = val[2:].decode('utf-8')
1860
elif val.startswith('s:'):
1861
val = val[2:].decode('base64')
1862
if err.error_verb == 'UnicodeDecodeError':
1863
raise UnicodeDecodeError(encoding, val, start, end, reason)
1864
elif err.error_verb == 'UnicodeEncodeError':
1865
raise UnicodeEncodeError(encoding, val, start, end, reason)
1866
elif err.error_verb == 'ReadOnlyError':
1867
raise errors.TransportNotPossible('readonly transport')
1868
raise errors.UnknownErrorFromSmartServer(err)