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
# A cache of looked up revision parent data; reset at unlock time.
323
self._parents_map = None
324
if 'hpss' in debug.debug_flags:
325
self._requested_parents = None
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):
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
352
return self._real_repository.abort_write_group()
354
def commit_write_group(self):
355
"""Complete a write group on the decorated repository.
357
Smart methods peform operations in a single step so this api
358
is not really applicable except as a compatibility thunk
359
for older plugins that don't use e.g. the CommitBuilder
363
return self._real_repository.commit_write_group()
365
def _ensure_real(self):
366
"""Ensure that there is a _real_repository set.
368
Used before calls to self._real_repository.
370
if self._real_repository is None:
371
self.bzrdir._ensure_real()
372
self._set_real_repository(
373
self.bzrdir._real_bzrdir.open_repository())
375
def _translate_error(self, err, **context):
376
self.bzrdir._translate_error(err, repository=self, **context)
378
def find_text_key_references(self):
379
"""Find the text key references within the repository.
381
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
382
revision_ids. Each altered file-ids has the exact revision_ids that
383
altered it listed explicitly.
384
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
385
to whether they were referred to by the inventory of the
386
revision_id that they contain. The inventory texts from all present
387
revision ids are assessed to generate this report.
390
return self._real_repository.find_text_key_references()
392
def _generate_text_key_index(self):
393
"""Generate a new text key index for the repository.
395
This is an expensive function that will take considerable time to run.
397
:return: A dict mapping (file_id, revision_id) tuples to a list of
398
parents, also (file_id, revision_id) tuples.
401
return self._real_repository._generate_text_key_index()
403
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
404
def get_revision_graph(self, revision_id=None):
405
"""See Repository.get_revision_graph()."""
406
return self._get_revision_graph(revision_id)
408
def _get_revision_graph(self, revision_id):
409
"""Private method for using with old (< 1.2) servers to fallback."""
410
if revision_id is None:
412
elif revision.is_null(revision_id):
415
path = self.bzrdir._path_for_remote_call(self._client)
416
response = self._call_expecting_body(
417
'Repository.get_revision_graph', path, revision_id)
418
response_tuple, response_handler = response
419
if response_tuple[0] != 'ok':
420
raise errors.UnexpectedSmartServerResponse(response_tuple)
421
coded = response_handler.read_body_bytes()
423
# no revisions in this repository!
425
lines = coded.split('\n')
428
d = tuple(line.split())
429
revision_graph[d[0]] = d[1:]
431
return revision_graph
433
def has_revision(self, revision_id):
434
"""See Repository.has_revision()."""
435
if revision_id == NULL_REVISION:
436
# The null revision is always present.
438
path = self.bzrdir._path_for_remote_call(self._client)
439
response = self._call('Repository.has_revision', path, revision_id)
440
if response[0] not in ('yes', 'no'):
441
raise errors.UnexpectedSmartServerResponse(response)
442
if response[0] == 'yes':
444
for fallback_repo in self._fallback_repositories:
445
if fallback_repo.has_revision(revision_id):
449
def has_revisions(self, revision_ids):
450
"""See Repository.has_revisions()."""
451
# FIXME: This does many roundtrips, particularly when there are
452
# fallback repositories. -- mbp 20080905
454
for revision_id in revision_ids:
455
if self.has_revision(revision_id):
456
result.add(revision_id)
459
def has_same_location(self, other):
460
return (self.__class__ == other.__class__ and
461
self.bzrdir.transport.base == other.bzrdir.transport.base)
463
def get_graph(self, other_repository=None):
464
"""Return the graph for this repository format"""
465
parents_provider = self
466
if (other_repository is not None and
467
other_repository.bzrdir.transport.base !=
468
self.bzrdir.transport.base):
469
parents_provider = graph._StackedParentsProvider(
470
[parents_provider, other_repository._make_parents_provider()])
471
return graph.Graph(parents_provider)
473
def gather_stats(self, revid=None, committers=None):
474
"""See Repository.gather_stats()."""
475
path = self.bzrdir._path_for_remote_call(self._client)
476
# revid can be None to indicate no revisions, not just NULL_REVISION
477
if revid is None or revision.is_null(revid):
481
if committers is None or not committers:
482
fmt_committers = 'no'
484
fmt_committers = 'yes'
485
response_tuple, response_handler = self._call_expecting_body(
486
'Repository.gather_stats', path, fmt_revid, fmt_committers)
487
if response_tuple[0] != 'ok':
488
raise errors.UnexpectedSmartServerResponse(response_tuple)
490
body = response_handler.read_body_bytes()
492
for line in body.split('\n'):
495
key, val_text = line.split(':')
496
if key in ('revisions', 'size', 'committers'):
497
result[key] = int(val_text)
498
elif key in ('firstrev', 'latestrev'):
499
values = val_text.split(' ')[1:]
500
result[key] = (float(values[0]), long(values[1]))
504
def find_branches(self, using=False):
505
"""See Repository.find_branches()."""
506
# should be an API call to the server.
508
return self._real_repository.find_branches(using=using)
510
def get_physical_lock_status(self):
511
"""See Repository.get_physical_lock_status()."""
512
# should be an API call to the server.
514
return self._real_repository.get_physical_lock_status()
516
def is_in_write_group(self):
517
"""Return True if there is an open write group.
519
write groups are only applicable locally for the smart server..
521
if self._real_repository:
522
return self._real_repository.is_in_write_group()
525
return self._lock_count >= 1
528
"""See Repository.is_shared()."""
529
path = self.bzrdir._path_for_remote_call(self._client)
530
response = self._call('Repository.is_shared', path)
531
if response[0] not in ('yes', 'no'):
532
raise SmartProtocolError('unexpected response code %s' % (response,))
533
return response[0] == 'yes'
535
def is_write_locked(self):
536
return self._lock_mode == 'w'
539
# wrong eventually - want a local lock cache context
540
if not self._lock_mode:
541
self._lock_mode = 'r'
543
self._parents_map = {}
544
if 'hpss' in debug.debug_flags:
545
self._requested_parents = set()
546
if self._real_repository is not None:
547
self._real_repository.lock_read()
549
self._lock_count += 1
551
def _remote_lock_write(self, token):
552
path = self.bzrdir._path_for_remote_call(self._client)
555
err_context = {'token': token}
556
response = self._call('Repository.lock_write', path, token,
558
if response[0] == 'ok':
562
raise errors.UnexpectedSmartServerResponse(response)
564
def lock_write(self, token=None, _skip_rpc=False):
565
if not self._lock_mode:
567
if self._lock_token is not None:
568
if token != self._lock_token:
569
raise errors.TokenMismatch(token, self._lock_token)
570
self._lock_token = token
572
self._lock_token = self._remote_lock_write(token)
573
# if self._lock_token is None, then this is something like packs or
574
# svn where we don't get to lock the repo, or a weave style repository
575
# where we cannot lock it over the wire and attempts to do so will
577
if self._real_repository is not None:
578
self._real_repository.lock_write(token=self._lock_token)
579
if token is not None:
580
self._leave_lock = True
582
self._leave_lock = False
583
self._lock_mode = 'w'
585
self._parents_map = {}
586
if 'hpss' in debug.debug_flags:
587
self._requested_parents = set()
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._parents_map = None
653
if 'hpss' in debug.debug_flags:
654
self._requested_parents = None
655
old_mode = self._lock_mode
656
self._lock_mode = None
658
# The real repository is responsible at present for raising an
659
# exception if it's in an unfinished write group. However, it
660
# normally will *not* actually remove the lock from disk - that's
661
# done by the server on receiving the Repository.unlock call.
662
# This is just to let the _real_repository stay up to date.
663
if self._real_repository is not None:
664
self._real_repository.unlock()
666
# The rpc-level lock should be released even if there was a
667
# problem releasing the vfs-based lock.
669
# Only write-locked repositories need to make a remote method
670
# call to perfom the unlock.
671
old_token = self._lock_token
672
self._lock_token = None
673
if not self._leave_lock:
674
self._unlock(old_token)
676
def break_lock(self):
677
# should hand off to the network
679
return self._real_repository.break_lock()
681
def _get_tarball(self, compression):
682
"""Return a TemporaryFile containing a repository tarball.
684
Returns None if the server does not support sending tarballs.
687
path = self.bzrdir._path_for_remote_call(self._client)
689
response, protocol = self._call_expecting_body(
690
'Repository.tarball', path, compression)
691
except errors.UnknownSmartMethod:
692
protocol.cancel_read_body()
694
if response[0] == 'ok':
695
# Extract the tarball and return it
696
t = tempfile.NamedTemporaryFile()
697
# TODO: rpc layer should read directly into it...
698
t.write(protocol.read_body_bytes())
701
raise errors.UnexpectedSmartServerResponse(response)
703
def sprout(self, to_bzrdir, revision_id=None):
704
# TODO: Option to control what format is created?
706
dest_repo = self._real_repository._format.initialize(to_bzrdir,
708
dest_repo.fetch(self, revision_id=revision_id)
711
### These methods are just thin shims to the VFS object for now.
713
def revision_tree(self, revision_id):
715
return self._real_repository.revision_tree(revision_id)
717
def get_serializer_format(self):
719
return self._real_repository.get_serializer_format()
721
def get_commit_builder(self, branch, parents, config, timestamp=None,
722
timezone=None, committer=None, revprops=None,
724
# FIXME: It ought to be possible to call this without immediately
725
# triggering _ensure_real. For now it's the easiest thing to do.
727
real_repo = self._real_repository
728
builder = real_repo.get_commit_builder(branch, parents,
729
config, timestamp=timestamp, timezone=timezone,
730
committer=committer, revprops=revprops, revision_id=revision_id)
733
def add_fallback_repository(self, repository):
734
"""Add a repository to use for looking up data not held locally.
736
:param repository: A repository.
738
# XXX: At the moment the RemoteRepository will allow fallbacks
739
# unconditionally - however, a _real_repository will usually exist,
740
# and may raise an error if it's not accommodated by the underlying
741
# format. Eventually we should check when opening the repository
742
# whether it's willing to allow them or not.
744
# We need to accumulate additional repositories here, to pass them in
746
self._fallback_repositories.append(repository)
747
# They are also seen by the fallback repository. If it doesn't exist
748
# yet they'll be added then. This implicitly copies them.
751
def add_inventory(self, revid, inv, parents):
753
return self._real_repository.add_inventory(revid, inv, parents)
755
def add_revision(self, rev_id, rev, inv=None, config=None):
757
return self._real_repository.add_revision(
758
rev_id, rev, inv=inv, config=config)
761
def get_inventory(self, revision_id):
763
return self._real_repository.get_inventory(revision_id)
765
def iter_inventories(self, revision_ids):
767
return self._real_repository.iter_inventories(revision_ids)
770
def get_revision(self, revision_id):
772
return self._real_repository.get_revision(revision_id)
774
def get_transaction(self):
776
return self._real_repository.get_transaction()
779
def clone(self, a_bzrdir, revision_id=None):
781
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
783
def make_working_trees(self):
784
"""See Repository.make_working_trees"""
786
return self._real_repository.make_working_trees()
788
def revision_ids_to_search_result(self, result_set):
789
"""Convert a set of revision ids to a graph SearchResult."""
790
result_parents = set()
791
for parents in self.get_graph().get_parent_map(
792
result_set).itervalues():
793
result_parents.update(parents)
794
included_keys = result_set.intersection(result_parents)
795
start_keys = result_set.difference(included_keys)
796
exclude_keys = result_parents.difference(result_set)
797
result = graph.SearchResult(start_keys, exclude_keys,
798
len(result_set), result_set)
802
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
803
"""Return the revision ids that other has that this does not.
805
These are returned in topological order.
807
revision_id: only return revision ids included by revision_id.
809
return repository.InterRepository.get(
810
other, self).search_missing_revision_ids(revision_id, find_ghosts)
812
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
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)
824
except NotImplementedError:
825
raise errors.IncompatibleRepositories(source, self)
827
def create_bundle(self, target, base, fileobj, format=None):
829
self._real_repository.create_bundle(target, base, fileobj, format)
832
def get_ancestry(self, revision_id, topo_sorted=True):
834
return self._real_repository.get_ancestry(revision_id, topo_sorted)
836
def fileids_altered_by_revision_ids(self, revision_ids):
838
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
840
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
842
return self._real_repository._get_versioned_file_checker(
843
revisions, revision_versions_cache)
845
def iter_files_bytes(self, desired_files):
846
"""See Repository.iter_file_bytes.
849
return self._real_repository.iter_files_bytes(desired_files)
852
def _fetch_order(self):
853
"""Decorate the real repository for now.
855
In the long term getting this back from the remote repository as part
856
of open would be more efficient.
859
return self._real_repository._fetch_order
862
def _fetch_uses_deltas(self):
863
"""Decorate the real repository for now.
865
In the long term getting this back from the remote repository as part
866
of open would be more efficient.
869
return self._real_repository._fetch_uses_deltas
872
def _fetch_reconcile(self):
873
"""Decorate the real repository for now.
875
In the long term getting this back from the remote repository as part
876
of open would be more efficient.
879
return self._real_repository._fetch_reconcile
881
def get_parent_map(self, keys):
882
"""See bzrlib.Graph.get_parent_map()."""
883
# Hack to build up the caching logic.
884
ancestry = self._parents_map
886
# Repository is not locked, so there's no cache.
887
missing_revisions = set(keys)
890
missing_revisions = set(key for key in keys if key not in ancestry)
891
if missing_revisions:
892
parent_map = self._get_parent_map(missing_revisions)
893
if 'hpss' in debug.debug_flags:
894
mutter('retransmitted revisions: %d of %d',
895
len(set(ancestry).intersection(parent_map)),
897
ancestry.update(parent_map)
898
present_keys = [k for k in keys if k in ancestry]
899
if 'hpss' in debug.debug_flags:
900
if self._requested_parents is not None and len(ancestry) != 0:
901
self._requested_parents.update(present_keys)
902
mutter('Current RemoteRepository graph hit rate: %d%%',
903
100.0 * len(self._requested_parents) / len(ancestry))
904
return dict((k, ancestry[k]) for k in present_keys)
906
def _get_parent_map(self, keys):
907
"""Helper for get_parent_map that performs the RPC."""
908
medium = self._client._medium
909
if medium._is_remote_before((1, 2)):
910
# We already found out that the server can't understand
911
# Repository.get_parent_map requests, so just fetch the whole
913
# XXX: Note that this will issue a deprecation warning. This is ok
914
# :- its because we're working with a deprecated server anyway, and
915
# the user will almost certainly have seen a warning about the
916
# server version already.
917
rg = self.get_revision_graph()
918
# There is an api discrepency between get_parent_map and
919
# get_revision_graph. Specifically, a "key:()" pair in
920
# get_revision_graph just means a node has no parents. For
921
# "get_parent_map" it means the node is a ghost. So fix up the
922
# graph to correct this.
923
# https://bugs.launchpad.net/bzr/+bug/214894
924
# There is one other "bug" which is that ghosts in
925
# get_revision_graph() are not returned at all. But we won't worry
926
# about that for now.
927
for node_id, parent_ids in rg.iteritems():
929
rg[node_id] = (NULL_REVISION,)
930
rg[NULL_REVISION] = ()
935
raise ValueError('get_parent_map(None) is not valid')
936
if NULL_REVISION in keys:
937
keys.discard(NULL_REVISION)
938
found_parents = {NULL_REVISION:()}
943
# TODO(Needs analysis): We could assume that the keys being requested
944
# from get_parent_map are in a breadth first search, so typically they
945
# will all be depth N from some common parent, and we don't have to
946
# have the server iterate from the root parent, but rather from the
947
# keys we're searching; and just tell the server the keyspace we
948
# already have; but this may be more traffic again.
950
# Transform self._parents_map into a search request recipe.
951
# TODO: Manage this incrementally to avoid covering the same path
952
# repeatedly. (The server will have to on each request, but the less
953
# work done the better).
954
parents_map = self._parents_map
955
if parents_map is None:
956
# Repository is not locked, so there's no cache.
958
start_set = set(parents_map)
959
result_parents = set()
960
for parents in parents_map.itervalues():
961
result_parents.update(parents)
962
stop_keys = result_parents.difference(start_set)
963
included_keys = start_set.intersection(result_parents)
964
start_set.difference_update(included_keys)
965
recipe = (start_set, stop_keys, len(parents_map))
966
body = self._serialise_search_recipe(recipe)
967
path = self.bzrdir._path_for_remote_call(self._client)
969
if type(key) is not str:
971
"key %r not a plain string" % (key,))
972
verb = 'Repository.get_parent_map'
973
args = (path,) + tuple(keys)
975
response = self._call_with_body_bytes_expecting_body(
976
verb, args, self._serialise_search_recipe(recipe))
977
except errors.UnknownSmartMethod:
978
# Server does not support this method, so get the whole graph.
979
# Worse, we have to force a disconnection, because the server now
980
# doesn't realise it has a body on the wire to consume, so the
981
# only way to recover is to abandon the connection.
983
'Server is too old for fast get_parent_map, reconnecting. '
984
'(Upgrade the server to Bazaar 1.2 to avoid this)')
986
# To avoid having to disconnect repeatedly, we keep track of the
987
# fact the server doesn't understand remote methods added in 1.2.
988
medium._remember_remote_is_before((1, 2))
989
return self.get_revision_graph(None)
990
response_tuple, response_handler = response
991
if response_tuple[0] not in ['ok']:
992
response_handler.cancel_read_body()
993
raise errors.UnexpectedSmartServerResponse(response_tuple)
994
if response_tuple[0] == 'ok':
995
coded = bz2.decompress(response_handler.read_body_bytes())
999
lines = coded.split('\n')
1002
d = tuple(line.split())
1004
revision_graph[d[0]] = d[1:]
1006
# No parents - so give the Graph result (NULL_REVISION,).
1007
revision_graph[d[0]] = (NULL_REVISION,)
1008
return revision_graph
1011
def get_signature_text(self, revision_id):
1013
return self._real_repository.get_signature_text(revision_id)
1016
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1017
def get_revision_graph_with_ghosts(self, revision_ids=None):
1019
return self._real_repository.get_revision_graph_with_ghosts(
1020
revision_ids=revision_ids)
1023
def get_inventory_xml(self, revision_id):
1025
return self._real_repository.get_inventory_xml(revision_id)
1027
def deserialise_inventory(self, revision_id, xml):
1029
return self._real_repository.deserialise_inventory(revision_id, xml)
1031
def reconcile(self, other=None, thorough=False):
1033
return self._real_repository.reconcile(other=other, thorough=thorough)
1035
def all_revision_ids(self):
1037
return self._real_repository.all_revision_ids()
1040
def get_deltas_for_revisions(self, revisions):
1042
return self._real_repository.get_deltas_for_revisions(revisions)
1045
def get_revision_delta(self, revision_id):
1047
return self._real_repository.get_revision_delta(revision_id)
1050
def revision_trees(self, revision_ids):
1052
return self._real_repository.revision_trees(revision_ids)
1055
def get_revision_reconcile(self, revision_id):
1057
return self._real_repository.get_revision_reconcile(revision_id)
1060
def check(self, revision_ids=None):
1062
return self._real_repository.check(revision_ids=revision_ids)
1064
def copy_content_into(self, destination, revision_id=None):
1066
return self._real_repository.copy_content_into(
1067
destination, revision_id=revision_id)
1069
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1070
# get a tarball of the remote repository, and copy from that into the
1072
from bzrlib import osutils
1074
# TODO: Maybe a progress bar while streaming the tarball?
1075
note("Copying repository content as tarball...")
1076
tar_file = self._get_tarball('bz2')
1077
if tar_file is None:
1079
destination = to_bzrdir.create_repository()
1081
tar = tarfile.open('repository', fileobj=tar_file,
1083
tmpdir = osutils.mkdtemp()
1085
_extract_tar(tar, tmpdir)
1086
tmp_bzrdir = BzrDir.open(tmpdir)
1087
tmp_repo = tmp_bzrdir.open_repository()
1088
tmp_repo.copy_content_into(destination, revision_id)
1090
osutils.rmtree(tmpdir)
1094
# TODO: Suggestion from john: using external tar is much faster than
1095
# python's tarfile library, but it may not work on windows.
1098
def inventories(self):
1099
"""Decorate the real repository for now.
1101
In the long term a full blown network facility is needed to
1102
avoid creating a real repository object locally.
1105
return self._real_repository.inventories
1109
"""Compress the data within the repository.
1111
This is not currently implemented within the smart server.
1114
return self._real_repository.pack()
1117
def revisions(self):
1118
"""Decorate the real repository for now.
1120
In the short term this should become a real object to intercept graph
1123
In the long term a full blown network facility is needed.
1126
return self._real_repository.revisions
1128
def set_make_working_trees(self, new_value):
1130
self._real_repository.set_make_working_trees(new_value)
1133
def signatures(self):
1134
"""Decorate the real repository for now.
1136
In the long term a full blown network facility is needed to avoid
1137
creating a real repository object locally.
1140
return self._real_repository.signatures
1143
def sign_revision(self, revision_id, gpg_strategy):
1145
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1149
"""Decorate the real repository for now.
1151
In the long term a full blown network facility is needed to avoid
1152
creating a real repository object locally.
1155
return self._real_repository.texts
1158
def get_revisions(self, revision_ids):
1160
return self._real_repository.get_revisions(revision_ids)
1162
def supports_rich_root(self):
1164
return self._real_repository.supports_rich_root()
1166
def iter_reverse_revision_history(self, revision_id):
1168
return self._real_repository.iter_reverse_revision_history(revision_id)
1171
def _serializer(self):
1173
return self._real_repository._serializer
1175
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1177
return self._real_repository.store_revision_signature(
1178
gpg_strategy, plaintext, revision_id)
1180
def add_signature_text(self, revision_id, signature):
1182
return self._real_repository.add_signature_text(revision_id, signature)
1184
def has_signature_for_revision_id(self, revision_id):
1186
return self._real_repository.has_signature_for_revision_id(revision_id)
1188
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1190
return self._real_repository.item_keys_introduced_by(revision_ids,
1191
_files_pb=_files_pb)
1193
def revision_graph_can_have_wrong_parents(self):
1194
# The answer depends on the remote repo format.
1196
return self._real_repository.revision_graph_can_have_wrong_parents()
1198
def _find_inconsistent_revision_parents(self):
1200
return self._real_repository._find_inconsistent_revision_parents()
1202
def _check_for_inconsistent_revision_parents(self):
1204
return self._real_repository._check_for_inconsistent_revision_parents()
1206
def _make_parents_provider(self):
1209
def _serialise_search_recipe(self, recipe):
1210
"""Serialise a graph search recipe.
1212
:param recipe: A search recipe (start, stop, count).
1213
:return: Serialised bytes.
1215
start_keys = ' '.join(recipe[0])
1216
stop_keys = ' '.join(recipe[1])
1217
count = str(recipe[2])
1218
return '\n'.join((start_keys, stop_keys, count))
1221
class RemoteBranchLockableFiles(LockableFiles):
1222
"""A 'LockableFiles' implementation that talks to a smart server.
1224
This is not a public interface class.
1227
def __init__(self, bzrdir, _client):
1228
self.bzrdir = bzrdir
1229
self._client = _client
1230
self._need_find_modes = True
1231
LockableFiles.__init__(
1232
self, bzrdir.get_branch_transport(None),
1233
'lock', lockdir.LockDir)
1235
def _find_modes(self):
1236
# RemoteBranches don't let the client set the mode of control files.
1237
self._dir_mode = None
1238
self._file_mode = None
1241
class RemoteBranchFormat(branch.BranchFormat):
1243
def __eq__(self, other):
1244
return (isinstance(other, RemoteBranchFormat) and
1245
self.__dict__ == other.__dict__)
1247
def get_format_description(self):
1248
return 'Remote BZR Branch'
1250
def get_format_string(self):
1251
return 'Remote BZR Branch'
1253
def open(self, a_bzrdir):
1254
return a_bzrdir.open_branch()
1256
def initialize(self, a_bzrdir):
1257
return a_bzrdir.create_branch()
1259
def supports_tags(self):
1260
# Remote branches might support tags, but we won't know until we
1261
# access the real remote branch.
1265
class RemoteBranch(branch.Branch, _RpcHelper):
1266
"""Branch stored on a server accessed by HPSS RPC.
1268
At the moment most operations are mapped down to simple file operations.
1271
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1273
"""Create a RemoteBranch instance.
1275
:param real_branch: An optional local implementation of the branch
1276
format, usually accessing the data via the VFS.
1277
:param _client: Private parameter for testing.
1279
# We intentionally don't call the parent class's __init__, because it
1280
# will try to assign to self.tags, which is a property in this subclass.
1281
# And the parent's __init__ doesn't do much anyway.
1282
self._revision_id_to_revno_cache = None
1283
self._revision_history_cache = None
1284
self._last_revision_info_cache = None
1285
self.bzrdir = remote_bzrdir
1286
if _client is not None:
1287
self._client = _client
1289
self._client = remote_bzrdir._client
1290
self.repository = remote_repository
1291
if real_branch is not None:
1292
self._real_branch = real_branch
1293
# Give the remote repository the matching real repo.
1294
real_repo = self._real_branch.repository
1295
if isinstance(real_repo, RemoteRepository):
1296
real_repo._ensure_real()
1297
real_repo = real_repo._real_repository
1298
self.repository._set_real_repository(real_repo)
1299
# Give the branch the remote repository to let fast-pathing happen.
1300
self._real_branch.repository = self.repository
1302
self._real_branch = None
1303
# Fill out expected attributes of branch for bzrlib api users.
1304
self._format = RemoteBranchFormat()
1305
self.base = self.bzrdir.root_transport.base
1306
self._control_files = None
1307
self._lock_mode = None
1308
self._lock_token = None
1309
self._repo_lock_token = None
1310
self._lock_count = 0
1311
self._leave_lock = False
1312
# The base class init is not called, so we duplicate this:
1313
hooks = branch.Branch.hooks['open']
1316
self._setup_stacking()
1318
def _setup_stacking(self):
1319
# configure stacking into the remote repository, by reading it from
1322
fallback_url = self.get_stacked_on_url()
1323
except (errors.NotStacked, errors.UnstackableBranchFormat,
1324
errors.UnstackableRepositoryFormat), e:
1326
# it's relative to this branch...
1327
fallback_url = urlutils.join(self.base, fallback_url)
1328
transports = [self.bzrdir.root_transport]
1329
if self._real_branch is not None:
1330
transports.append(self._real_branch._transport)
1331
fallback_bzrdir = BzrDir.open(fallback_url, transports)
1332
fallback_repo = fallback_bzrdir.open_repository()
1333
self.repository.add_fallback_repository(fallback_repo)
1335
def _get_real_transport(self):
1336
# if we try vfs access, return the real branch's vfs transport
1338
return self._real_branch._transport
1340
_transport = property(_get_real_transport)
1343
return "%s(%s)" % (self.__class__.__name__, self.base)
1347
def _ensure_real(self):
1348
"""Ensure that there is a _real_branch set.
1350
Used before calls to self._real_branch.
1352
if self._real_branch is None:
1353
if not vfs.vfs_enabled():
1354
raise AssertionError('smart server vfs must be enabled '
1355
'to use vfs implementation')
1356
self.bzrdir._ensure_real()
1357
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1358
if self.repository._real_repository is None:
1359
# Give the remote repository the matching real repo.
1360
real_repo = self._real_branch.repository
1361
if isinstance(real_repo, RemoteRepository):
1362
real_repo._ensure_real()
1363
real_repo = real_repo._real_repository
1364
self.repository._set_real_repository(real_repo)
1365
# Give the real branch the remote repository to let fast-pathing
1367
self._real_branch.repository = self.repository
1368
if self._lock_mode == 'r':
1369
self._real_branch.lock_read()
1370
elif self._lock_mode == 'w':
1371
self._real_branch.lock_write(token=self._lock_token)
1373
def _translate_error(self, err, **context):
1374
self.repository._translate_error(err, branch=self, **context)
1376
def _clear_cached_state(self):
1377
super(RemoteBranch, self)._clear_cached_state()
1378
if self._real_branch is not None:
1379
self._real_branch._clear_cached_state()
1381
def _clear_cached_state_of_remote_branch_only(self):
1382
"""Like _clear_cached_state, but doesn't clear the cache of
1385
This is useful when falling back to calling a method of
1386
self._real_branch that changes state. In that case the underlying
1387
branch changes, so we need to invalidate this RemoteBranch's cache of
1388
it. However, there's no need to invalidate the _real_branch's cache
1389
too, in fact doing so might harm performance.
1391
super(RemoteBranch, self)._clear_cached_state()
1394
def control_files(self):
1395
# Defer actually creating RemoteBranchLockableFiles until its needed,
1396
# because it triggers an _ensure_real that we otherwise might not need.
1397
if self._control_files is None:
1398
self._control_files = RemoteBranchLockableFiles(
1399
self.bzrdir, self._client)
1400
return self._control_files
1402
def _get_checkout_format(self):
1404
return self._real_branch._get_checkout_format()
1406
def get_physical_lock_status(self):
1407
"""See Branch.get_physical_lock_status()."""
1408
# should be an API call to the server, as branches must be lockable.
1410
return self._real_branch.get_physical_lock_status()
1412
def get_stacked_on_url(self):
1413
"""Get the URL this branch is stacked against.
1415
:raises NotStacked: If the branch is not stacked.
1416
:raises UnstackableBranchFormat: If the branch does not support
1418
:raises UnstackableRepositoryFormat: If the repository does not support
1422
# there may not be a repository yet, so we can't use
1423
# self._translate_error, so we can't use self._call either.
1424
response = self._client.call('Branch.get_stacked_on_url',
1425
self._remote_path())
1426
except errors.ErrorFromSmartServer, err:
1427
# there may not be a repository yet, so we can't call through
1428
# its _translate_error
1429
_translate_error(err, branch=self)
1430
except errors.UnknownSmartMethod, err:
1432
return self._real_branch.get_stacked_on_url()
1433
if response[0] != 'ok':
1434
raise errors.UnexpectedSmartServerResponse(response)
1437
def lock_read(self):
1438
self.repository.lock_read()
1439
if not self._lock_mode:
1440
self._lock_mode = 'r'
1441
self._lock_count = 1
1442
if self._real_branch is not None:
1443
self._real_branch.lock_read()
1445
self._lock_count += 1
1447
def _remote_lock_write(self, token):
1449
branch_token = repo_token = ''
1451
branch_token = token
1452
repo_token = self.repository.lock_write()
1453
self.repository.unlock()
1454
err_context = {'token': token}
1455
response = self._call(
1456
'Branch.lock_write', self._remote_path(), branch_token,
1457
repo_token or '', **err_context)
1458
if response[0] != 'ok':
1459
raise errors.UnexpectedSmartServerResponse(response)
1460
ok, branch_token, repo_token = response
1461
return branch_token, repo_token
1463
def lock_write(self, token=None):
1464
if not self._lock_mode:
1465
# Lock the branch and repo in one remote call.
1466
remote_tokens = self._remote_lock_write(token)
1467
self._lock_token, self._repo_lock_token = remote_tokens
1468
if not self._lock_token:
1469
raise SmartProtocolError('Remote server did not return a token!')
1470
# Tell the self.repository object that it is locked.
1471
self.repository.lock_write(
1472
self._repo_lock_token, _skip_rpc=True)
1474
if self._real_branch is not None:
1475
self._real_branch.lock_write(token=self._lock_token)
1476
if token is not None:
1477
self._leave_lock = True
1479
self._leave_lock = False
1480
self._lock_mode = 'w'
1481
self._lock_count = 1
1482
elif self._lock_mode == 'r':
1483
raise errors.ReadOnlyTransaction
1485
if token is not None:
1486
# A token was given to lock_write, and we're relocking, so
1487
# check that the given token actually matches the one we
1489
if token != self._lock_token:
1490
raise errors.TokenMismatch(token, self._lock_token)
1491
self._lock_count += 1
1492
# Re-lock the repository too.
1493
self.repository.lock_write(self._repo_lock_token)
1494
return self._lock_token or None
1496
def _unlock(self, branch_token, repo_token):
1497
err_context = {'token': str((branch_token, repo_token))}
1498
response = self._call(
1499
'Branch.unlock', self._remote_path(), branch_token,
1500
repo_token or '', **err_context)
1501
if response == ('ok',):
1503
raise errors.UnexpectedSmartServerResponse(response)
1507
self._lock_count -= 1
1508
if not self._lock_count:
1509
self._clear_cached_state()
1510
mode = self._lock_mode
1511
self._lock_mode = None
1512
if self._real_branch is not None:
1513
if (not self._leave_lock and mode == 'w' and
1514
self._repo_lock_token):
1515
# If this RemoteBranch will remove the physical lock
1516
# for the repository, make sure the _real_branch
1517
# doesn't do it first. (Because the _real_branch's
1518
# repository is set to be the RemoteRepository.)
1519
self._real_branch.repository.leave_lock_in_place()
1520
self._real_branch.unlock()
1522
# Only write-locked branched need to make a remote method
1523
# call to perfom the unlock.
1525
if not self._lock_token:
1526
raise AssertionError('Locked, but no token!')
1527
branch_token = self._lock_token
1528
repo_token = self._repo_lock_token
1529
self._lock_token = None
1530
self._repo_lock_token = None
1531
if not self._leave_lock:
1532
self._unlock(branch_token, repo_token)
1534
self.repository.unlock()
1536
def break_lock(self):
1538
return self._real_branch.break_lock()
1540
def leave_lock_in_place(self):
1541
if not self._lock_token:
1542
raise NotImplementedError(self.leave_lock_in_place)
1543
self._leave_lock = True
1545
def dont_leave_lock_in_place(self):
1546
if not self._lock_token:
1547
raise NotImplementedError(self.dont_leave_lock_in_place)
1548
self._leave_lock = False
1550
def _last_revision_info(self):
1551
response = self._call('Branch.last_revision_info', self._remote_path())
1552
if response[0] != 'ok':
1553
raise SmartProtocolError('unexpected response code %s' % (response,))
1554
revno = int(response[1])
1555
last_revision = response[2]
1556
return (revno, last_revision)
1558
def _gen_revision_history(self):
1559
"""See Branch._gen_revision_history()."""
1560
response_tuple, response_handler = self._call_expecting_body(
1561
'Branch.revision_history', self._remote_path())
1562
if response_tuple[0] != 'ok':
1563
raise errors.UnexpectedSmartServerResponse(response_tuple)
1564
result = response_handler.read_body_bytes().split('\x00')
1569
def _remote_path(self):
1570
return self.bzrdir._path_for_remote_call(self._client)
1572
def _set_last_revision_descendant(self, revision_id, other_branch,
1573
allow_diverged=False, allow_overwrite_descendant=False):
1574
err_context = {'other_branch': other_branch}
1575
response = self._call('Branch.set_last_revision_ex',
1576
self._remote_path(), self._lock_token, self._repo_lock_token,
1577
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1579
self._clear_cached_state()
1580
if len(response) != 3 and response[0] != 'ok':
1581
raise errors.UnexpectedSmartServerResponse(response)
1582
new_revno, new_revision_id = response[1:]
1583
self._last_revision_info_cache = new_revno, new_revision_id
1584
if self._real_branch is not None:
1585
cache = new_revno, new_revision_id
1586
self._real_branch._last_revision_info_cache = cache
1588
def _set_last_revision(self, revision_id):
1589
self._clear_cached_state()
1590
response = self._call('Branch.set_last_revision',
1591
self._remote_path(), self._lock_token, self._repo_lock_token,
1593
if response != ('ok',):
1594
raise errors.UnexpectedSmartServerResponse(response)
1597
def set_revision_history(self, rev_history):
1598
# Send just the tip revision of the history; the server will generate
1599
# the full history from that. If the revision doesn't exist in this
1600
# branch, NoSuchRevision will be raised.
1601
if rev_history == []:
1604
rev_id = rev_history[-1]
1605
self._set_last_revision(rev_id)
1606
self._cache_revision_history(rev_history)
1608
def get_parent(self):
1610
return self._real_branch.get_parent()
1612
def set_parent(self, url):
1614
return self._real_branch.set_parent(url)
1616
def set_stacked_on_url(self, stacked_location):
1617
"""Set the URL this branch is stacked against.
1619
:raises UnstackableBranchFormat: If the branch does not support
1621
:raises UnstackableRepositoryFormat: If the repository does not support
1625
return self._real_branch.set_stacked_on_url(stacked_location)
1627
def sprout(self, to_bzrdir, revision_id=None):
1628
branch_format = to_bzrdir._format._branch_format
1629
if (branch_format is None or
1630
isinstance(branch_format, RemoteBranchFormat)):
1631
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1632
# implies the same thing), but RemoteBranches can't be created at
1633
# arbitrary URLs. So create a branch in the same format as
1634
# _real_branch instead.
1635
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1636
# to_bzrdir.create_branch to create a RemoteBranch after all...
1638
result = self._real_branch._format.initialize(to_bzrdir)
1639
self.copy_content_into(result, revision_id=revision_id)
1640
result.set_parent(self.bzrdir.root_transport.base)
1642
result = branch.Branch.sprout(
1643
self, to_bzrdir, revision_id=revision_id)
1647
def pull(self, source, overwrite=False, stop_revision=None,
1649
self._clear_cached_state_of_remote_branch_only()
1651
return self._real_branch.pull(
1652
source, overwrite=overwrite, stop_revision=stop_revision,
1653
_override_hook_target=self, **kwargs)
1656
def push(self, target, overwrite=False, stop_revision=None):
1658
return self._real_branch.push(
1659
target, overwrite=overwrite, stop_revision=stop_revision,
1660
_override_hook_source_branch=self)
1662
def is_locked(self):
1663
return self._lock_count >= 1
1666
def revision_id_to_revno(self, revision_id):
1668
return self._real_branch.revision_id_to_revno(revision_id)
1671
def set_last_revision_info(self, revno, revision_id):
1672
revision_id = ensure_null(revision_id)
1674
response = self._call('Branch.set_last_revision_info',
1675
self._remote_path(), self._lock_token, self._repo_lock_token,
1676
str(revno), revision_id)
1677
except errors.UnknownSmartMethod:
1679
self._clear_cached_state_of_remote_branch_only()
1680
self._real_branch.set_last_revision_info(revno, revision_id)
1681
self._last_revision_info_cache = revno, revision_id
1683
if response == ('ok',):
1684
self._clear_cached_state()
1685
self._last_revision_info_cache = revno, revision_id
1686
# Update the _real_branch's cache too.
1687
if self._real_branch is not None:
1688
cache = self._last_revision_info_cache
1689
self._real_branch._last_revision_info_cache = cache
1691
raise errors.UnexpectedSmartServerResponse(response)
1694
def generate_revision_history(self, revision_id, last_rev=None,
1696
medium = self._client._medium
1697
if not medium._is_remote_before((1, 6)):
1699
self._set_last_revision_descendant(revision_id, other_branch,
1700
allow_diverged=True, allow_overwrite_descendant=True)
1702
except errors.UnknownSmartMethod:
1703
medium._remember_remote_is_before((1, 6))
1704
self._clear_cached_state_of_remote_branch_only()
1706
self._real_branch.generate_revision_history(
1707
revision_id, last_rev=last_rev, other_branch=other_branch)
1712
return self._real_branch.tags
1714
def set_push_location(self, location):
1716
return self._real_branch.set_push_location(location)
1719
def update_revisions(self, other, stop_revision=None, overwrite=False,
1721
"""See Branch.update_revisions."""
1724
if stop_revision is None:
1725
stop_revision = other.last_revision()
1726
if revision.is_null(stop_revision):
1727
# if there are no commits, we're done.
1729
self.fetch(other, stop_revision)
1732
# Just unconditionally set the new revision. We don't care if
1733
# the branches have diverged.
1734
self._set_last_revision(stop_revision)
1736
medium = self._client._medium
1737
if not medium._is_remote_before((1, 6)):
1739
self._set_last_revision_descendant(stop_revision, other)
1741
except errors.UnknownSmartMethod:
1742
medium._remember_remote_is_before((1, 6))
1743
# Fallback for pre-1.6 servers: check for divergence
1744
# client-side, then do _set_last_revision.
1745
last_rev = revision.ensure_null(self.last_revision())
1747
graph = self.repository.get_graph()
1748
if self._check_if_descendant_or_diverged(
1749
stop_revision, last_rev, graph, other):
1750
# stop_revision is a descendant of last_rev, but we aren't
1751
# overwriting, so we're done.
1753
self._set_last_revision(stop_revision)
1758
def _extract_tar(tar, to_dir):
1759
"""Extract all the contents of a tarfile object.
1761
A replacement for extractall, which is not present in python2.4
1764
tar.extract(tarinfo, to_dir)
1767
def _translate_error(err, **context):
1768
"""Translate an ErrorFromSmartServer into a more useful error.
1770
Possible context keys:
1778
If the error from the server doesn't match a known pattern, then
1779
UnknownErrorFromSmartServer is raised.
1783
return context[name]
1784
except KeyError, key_err:
1785
mutter('Missing key %r in context %r', key_err.args[0], context)
1788
"""Get the path from the context if present, otherwise use first error
1792
return context['path']
1793
except KeyError, key_err:
1795
return err.error_args[0]
1796
except IndexError, idx_err:
1798
'Missing key %r in context %r', key_err.args[0], context)
1801
if err.error_verb == 'NoSuchRevision':
1802
raise NoSuchRevision(find('branch'), err.error_args[0])
1803
elif err.error_verb == 'nosuchrevision':
1804
raise NoSuchRevision(find('repository'), err.error_args[0])
1805
elif err.error_tuple == ('nobranch',):
1806
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1807
elif err.error_verb == 'norepository':
1808
raise errors.NoRepositoryPresent(find('bzrdir'))
1809
elif err.error_verb == 'LockContention':
1810
raise errors.LockContention('(remote lock)')
1811
elif err.error_verb == 'UnlockableTransport':
1812
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1813
elif err.error_verb == 'LockFailed':
1814
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1815
elif err.error_verb == 'TokenMismatch':
1816
raise errors.TokenMismatch(find('token'), '(remote token)')
1817
elif err.error_verb == 'Diverged':
1818
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1819
elif err.error_verb == 'TipChangeRejected':
1820
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1821
elif err.error_verb == 'UnstackableBranchFormat':
1822
raise errors.UnstackableBranchFormat(*err.error_args)
1823
elif err.error_verb == 'UnstackableRepositoryFormat':
1824
raise errors.UnstackableRepositoryFormat(*err.error_args)
1825
elif err.error_verb == 'NotStacked':
1826
raise errors.NotStacked(branch=find('branch'))
1827
elif err.error_verb == 'PermissionDenied':
1829
if len(err.error_args) >= 2:
1830
extra = err.error_args[1]
1833
raise errors.PermissionDenied(path, extra=extra)
1834
elif err.error_verb == 'ReadError':
1836
raise errors.ReadError(path)
1837
elif err.error_verb == 'NoSuchFile':
1839
raise errors.NoSuchFile(path)
1840
elif err.error_verb == 'FileExists':
1841
raise errors.FileExists(err.error_args[0])
1842
elif err.error_verb == 'DirectoryNotEmpty':
1843
raise errors.DirectoryNotEmpty(err.error_args[0])
1844
elif err.error_verb == 'ShortReadvError':
1845
args = err.error_args
1846
raise errors.ShortReadvError(
1847
args[0], int(args[1]), int(args[2]), int(args[3]))
1848
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1849
encoding = str(err.error_args[0]) # encoding must always be a string
1850
val = err.error_args[1]
1851
start = int(err.error_args[2])
1852
end = int(err.error_args[3])
1853
reason = str(err.error_args[4]) # reason must always be a string
1854
if val.startswith('u:'):
1855
val = val[2:].decode('utf-8')
1856
elif val.startswith('s:'):
1857
val = val[2:].decode('base64')
1858
if err.error_verb == 'UnicodeDecodeError':
1859
raise UnicodeDecodeError(encoding, val, start, end, reason)
1860
elif err.error_verb == 'UnicodeEncodeError':
1861
raise UnicodeEncodeError(encoding, val, start, end, reason)
1862
elif err.error_verb == 'ReadOnlyError':
1863
raise errors.TransportNotPossible('readonly transport')
1864
raise errors.UnknownErrorFromSmartServer(err)