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, suppress_errors=False):
344
"""Complete a write group on the decorated repository.
346
Smart methods peform operations in a single step so this api
347
is not really applicable except as a compatibility thunk
348
for older plugins that don't use e.g. the CommitBuilder
352
return self._real_repository.abort_write_group(
353
suppress_errors=suppress_errors)
355
def commit_write_group(self):
356
"""Complete a write group on the decorated repository.
358
Smart methods peform operations in a single step so this api
359
is not really applicable except as a compatibility thunk
360
for older plugins that don't use e.g. the CommitBuilder
364
return self._real_repository.commit_write_group()
366
def _ensure_real(self):
367
"""Ensure that there is a _real_repository set.
369
Used before calls to self._real_repository.
371
if self._real_repository is None:
372
self.bzrdir._ensure_real()
373
self._set_real_repository(
374
self.bzrdir._real_bzrdir.open_repository())
376
def _translate_error(self, err, **context):
377
self.bzrdir._translate_error(err, repository=self, **context)
379
def find_text_key_references(self):
380
"""Find the text key references within the repository.
382
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
383
revision_ids. Each altered file-ids has the exact revision_ids that
384
altered it listed explicitly.
385
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
386
to whether they were referred to by the inventory of the
387
revision_id that they contain. The inventory texts from all present
388
revision ids are assessed to generate this report.
391
return self._real_repository.find_text_key_references()
393
def _generate_text_key_index(self):
394
"""Generate a new text key index for the repository.
396
This is an expensive function that will take considerable time to run.
398
:return: A dict mapping (file_id, revision_id) tuples to a list of
399
parents, also (file_id, revision_id) tuples.
402
return self._real_repository._generate_text_key_index()
404
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
405
def get_revision_graph(self, revision_id=None):
406
"""See Repository.get_revision_graph()."""
407
return self._get_revision_graph(revision_id)
409
def _get_revision_graph(self, revision_id):
410
"""Private method for using with old (< 1.2) servers to fallback."""
411
if revision_id is None:
413
elif revision.is_null(revision_id):
416
path = self.bzrdir._path_for_remote_call(self._client)
417
response = self._call_expecting_body(
418
'Repository.get_revision_graph', path, revision_id)
419
response_tuple, response_handler = response
420
if response_tuple[0] != 'ok':
421
raise errors.UnexpectedSmartServerResponse(response_tuple)
422
coded = response_handler.read_body_bytes()
424
# no revisions in this repository!
426
lines = coded.split('\n')
429
d = tuple(line.split())
430
revision_graph[d[0]] = d[1:]
432
return revision_graph
434
def has_revision(self, revision_id):
435
"""See Repository.has_revision()."""
436
if revision_id == NULL_REVISION:
437
# The null revision is always present.
439
path = self.bzrdir._path_for_remote_call(self._client)
440
response = self._call('Repository.has_revision', path, revision_id)
441
if response[0] not in ('yes', 'no'):
442
raise errors.UnexpectedSmartServerResponse(response)
443
if response[0] == 'yes':
445
for fallback_repo in self._fallback_repositories:
446
if fallback_repo.has_revision(revision_id):
450
def has_revisions(self, revision_ids):
451
"""See Repository.has_revisions()."""
452
# FIXME: This does many roundtrips, particularly when there are
453
# fallback repositories. -- mbp 20080905
455
for revision_id in revision_ids:
456
if self.has_revision(revision_id):
457
result.add(revision_id)
460
def has_same_location(self, other):
461
return (self.__class__ == other.__class__ and
462
self.bzrdir.transport.base == other.bzrdir.transport.base)
464
def get_graph(self, other_repository=None):
465
"""Return the graph for this repository format"""
466
parents_provider = self
467
if (other_repository is not None and
468
other_repository.bzrdir.transport.base !=
469
self.bzrdir.transport.base):
470
parents_provider = graph._StackedParentsProvider(
471
[parents_provider, other_repository._make_parents_provider()])
472
return graph.Graph(parents_provider)
474
def gather_stats(self, revid=None, committers=None):
475
"""See Repository.gather_stats()."""
476
path = self.bzrdir._path_for_remote_call(self._client)
477
# revid can be None to indicate no revisions, not just NULL_REVISION
478
if revid is None or revision.is_null(revid):
482
if committers is None or not committers:
483
fmt_committers = 'no'
485
fmt_committers = 'yes'
486
response_tuple, response_handler = self._call_expecting_body(
487
'Repository.gather_stats', path, fmt_revid, fmt_committers)
488
if response_tuple[0] != 'ok':
489
raise errors.UnexpectedSmartServerResponse(response_tuple)
491
body = response_handler.read_body_bytes()
493
for line in body.split('\n'):
496
key, val_text = line.split(':')
497
if key in ('revisions', 'size', 'committers'):
498
result[key] = int(val_text)
499
elif key in ('firstrev', 'latestrev'):
500
values = val_text.split(' ')[1:]
501
result[key] = (float(values[0]), long(values[1]))
505
def find_branches(self, using=False):
506
"""See Repository.find_branches()."""
507
# should be an API call to the server.
509
return self._real_repository.find_branches(using=using)
511
def get_physical_lock_status(self):
512
"""See Repository.get_physical_lock_status()."""
513
# should be an API call to the server.
515
return self._real_repository.get_physical_lock_status()
517
def is_in_write_group(self):
518
"""Return True if there is an open write group.
520
write groups are only applicable locally for the smart server..
522
if self._real_repository:
523
return self._real_repository.is_in_write_group()
526
return self._lock_count >= 1
529
"""See Repository.is_shared()."""
530
path = self.bzrdir._path_for_remote_call(self._client)
531
response = self._call('Repository.is_shared', path)
532
if response[0] not in ('yes', 'no'):
533
raise SmartProtocolError('unexpected response code %s' % (response,))
534
return response[0] == 'yes'
536
def is_write_locked(self):
537
return self._lock_mode == 'w'
540
# wrong eventually - want a local lock cache context
541
if not self._lock_mode:
542
self._lock_mode = 'r'
544
self._parents_map = {}
545
if 'hpss' in debug.debug_flags:
546
self._requested_parents = set()
547
if self._real_repository is not None:
548
self._real_repository.lock_read()
550
self._lock_count += 1
552
def _remote_lock_write(self, token):
553
path = self.bzrdir._path_for_remote_call(self._client)
556
err_context = {'token': token}
557
response = self._call('Repository.lock_write', path, token,
559
if response[0] == 'ok':
563
raise errors.UnexpectedSmartServerResponse(response)
565
def lock_write(self, token=None, _skip_rpc=False):
566
if not self._lock_mode:
568
if self._lock_token is not None:
569
if token != self._lock_token:
570
raise errors.TokenMismatch(token, self._lock_token)
571
self._lock_token = token
573
self._lock_token = self._remote_lock_write(token)
574
# if self._lock_token is None, then this is something like packs or
575
# svn where we don't get to lock the repo, or a weave style repository
576
# where we cannot lock it over the wire and attempts to do so will
578
if self._real_repository is not None:
579
self._real_repository.lock_write(token=self._lock_token)
580
if token is not None:
581
self._leave_lock = True
583
self._leave_lock = False
584
self._lock_mode = 'w'
586
self._parents_map = {}
587
if 'hpss' in debug.debug_flags:
588
self._requested_parents = set()
589
elif self._lock_mode == 'r':
590
raise errors.ReadOnlyError(self)
592
self._lock_count += 1
593
return self._lock_token or None
595
def leave_lock_in_place(self):
596
if not self._lock_token:
597
raise NotImplementedError(self.leave_lock_in_place)
598
self._leave_lock = True
600
def dont_leave_lock_in_place(self):
601
if not self._lock_token:
602
raise NotImplementedError(self.dont_leave_lock_in_place)
603
self._leave_lock = False
605
def _set_real_repository(self, repository):
606
"""Set the _real_repository for this repository.
608
:param repository: The repository to fallback to for non-hpss
609
implemented operations.
611
if self._real_repository is not None:
612
raise AssertionError('_real_repository is already set')
613
if isinstance(repository, RemoteRepository):
614
raise AssertionError()
615
self._real_repository = repository
616
for fb in self._fallback_repositories:
617
self._real_repository.add_fallback_repository(fb)
618
if self._lock_mode == 'w':
619
# if we are already locked, the real repository must be able to
620
# acquire the lock with our token.
621
self._real_repository.lock_write(self._lock_token)
622
elif self._lock_mode == 'r':
623
self._real_repository.lock_read()
625
def start_write_group(self):
626
"""Start a write group on the decorated repository.
628
Smart methods peform operations in a single step so this api
629
is not really applicable except as a compatibility thunk
630
for older plugins that don't use e.g. the CommitBuilder
634
return self._real_repository.start_write_group()
636
def _unlock(self, token):
637
path = self.bzrdir._path_for_remote_call(self._client)
639
# with no token the remote repository is not persistently locked.
641
err_context = {'token': token}
642
response = self._call('Repository.unlock', path, token,
644
if response == ('ok',):
647
raise errors.UnexpectedSmartServerResponse(response)
650
self._lock_count -= 1
651
if self._lock_count > 0:
653
self._parents_map = None
654
if 'hpss' in debug.debug_flags:
655
self._requested_parents = None
656
old_mode = self._lock_mode
657
self._lock_mode = None
659
# The real repository is responsible at present for raising an
660
# exception if it's in an unfinished write group. However, it
661
# normally will *not* actually remove the lock from disk - that's
662
# done by the server on receiving the Repository.unlock call.
663
# This is just to let the _real_repository stay up to date.
664
if self._real_repository is not None:
665
self._real_repository.unlock()
667
# The rpc-level lock should be released even if there was a
668
# problem releasing the vfs-based lock.
670
# Only write-locked repositories need to make a remote method
671
# call to perfom the unlock.
672
old_token = self._lock_token
673
self._lock_token = None
674
if not self._leave_lock:
675
self._unlock(old_token)
677
def break_lock(self):
678
# should hand off to the network
680
return self._real_repository.break_lock()
682
def _get_tarball(self, compression):
683
"""Return a TemporaryFile containing a repository tarball.
685
Returns None if the server does not support sending tarballs.
688
path = self.bzrdir._path_for_remote_call(self._client)
690
response, protocol = self._call_expecting_body(
691
'Repository.tarball', path, compression)
692
except errors.UnknownSmartMethod:
693
protocol.cancel_read_body()
695
if response[0] == 'ok':
696
# Extract the tarball and return it
697
t = tempfile.NamedTemporaryFile()
698
# TODO: rpc layer should read directly into it...
699
t.write(protocol.read_body_bytes())
702
raise errors.UnexpectedSmartServerResponse(response)
704
def sprout(self, to_bzrdir, revision_id=None):
705
# TODO: Option to control what format is created?
707
dest_repo = self._real_repository._format.initialize(to_bzrdir,
709
dest_repo.fetch(self, revision_id=revision_id)
712
### These methods are just thin shims to the VFS object for now.
714
def revision_tree(self, revision_id):
716
return self._real_repository.revision_tree(revision_id)
718
def get_serializer_format(self):
720
return self._real_repository.get_serializer_format()
722
def get_commit_builder(self, branch, parents, config, timestamp=None,
723
timezone=None, committer=None, revprops=None,
725
# FIXME: It ought to be possible to call this without immediately
726
# triggering _ensure_real. For now it's the easiest thing to do.
728
real_repo = self._real_repository
729
builder = real_repo.get_commit_builder(branch, parents,
730
config, timestamp=timestamp, timezone=timezone,
731
committer=committer, revprops=revprops, revision_id=revision_id)
734
def add_fallback_repository(self, repository):
735
"""Add a repository to use for looking up data not held locally.
737
:param repository: A repository.
739
# XXX: At the moment the RemoteRepository will allow fallbacks
740
# unconditionally - however, a _real_repository will usually exist,
741
# and may raise an error if it's not accommodated by the underlying
742
# format. Eventually we should check when opening the repository
743
# whether it's willing to allow them or not.
745
# We need to accumulate additional repositories here, to pass them in
747
self._fallback_repositories.append(repository)
748
# They are also seen by the fallback repository. If it doesn't exist
749
# yet they'll be added then. This implicitly copies them.
752
def add_inventory(self, revid, inv, parents):
754
return self._real_repository.add_inventory(revid, inv, parents)
756
def add_revision(self, rev_id, rev, inv=None, config=None):
758
return self._real_repository.add_revision(
759
rev_id, rev, inv=inv, config=config)
762
def get_inventory(self, revision_id):
764
return self._real_repository.get_inventory(revision_id)
766
def iter_inventories(self, revision_ids):
768
return self._real_repository.iter_inventories(revision_ids)
771
def get_revision(self, revision_id):
773
return self._real_repository.get_revision(revision_id)
775
def get_transaction(self):
777
return self._real_repository.get_transaction()
780
def clone(self, a_bzrdir, revision_id=None):
782
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
784
def make_working_trees(self):
785
"""See Repository.make_working_trees"""
787
return self._real_repository.make_working_trees()
789
def revision_ids_to_search_result(self, result_set):
790
"""Convert a set of revision ids to a graph SearchResult."""
791
result_parents = set()
792
for parents in self.get_graph().get_parent_map(
793
result_set).itervalues():
794
result_parents.update(parents)
795
included_keys = result_set.intersection(result_parents)
796
start_keys = result_set.difference(included_keys)
797
exclude_keys = result_parents.difference(result_set)
798
result = graph.SearchResult(start_keys, exclude_keys,
799
len(result_set), result_set)
803
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
804
"""Return the revision ids that other has that this does not.
806
These are returned in topological order.
808
revision_id: only return revision ids included by revision_id.
810
return repository.InterRepository.get(
811
other, self).search_missing_revision_ids(revision_id, find_ghosts)
813
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
814
# Not delegated to _real_repository so that InterRepository.get has a
815
# chance to find an InterRepository specialised for RemoteRepository.
816
if self.has_same_location(source):
817
# check that last_revision is in 'from' and then return a
819
if (revision_id is not None and
820
not revision.is_null(revision_id)):
821
self.get_revision(revision_id)
823
inter = repository.InterRepository.get(source, self)
825
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
826
except NotImplementedError:
827
raise errors.IncompatibleRepositories(source, self)
829
def create_bundle(self, target, base, fileobj, format=None):
831
self._real_repository.create_bundle(target, base, fileobj, format)
834
def get_ancestry(self, revision_id, topo_sorted=True):
836
return self._real_repository.get_ancestry(revision_id, topo_sorted)
838
def fileids_altered_by_revision_ids(self, revision_ids):
840
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
842
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
844
return self._real_repository._get_versioned_file_checker(
845
revisions, revision_versions_cache)
847
def iter_files_bytes(self, desired_files):
848
"""See Repository.iter_file_bytes.
851
return self._real_repository.iter_files_bytes(desired_files)
854
def _fetch_order(self):
855
"""Decorate the real repository for now.
857
In the long term getting this back from the remote repository as part
858
of open would be more efficient.
861
return self._real_repository._fetch_order
864
def _fetch_uses_deltas(self):
865
"""Decorate the real repository for now.
867
In the long term getting this back from the remote repository as part
868
of open would be more efficient.
871
return self._real_repository._fetch_uses_deltas
874
def _fetch_reconcile(self):
875
"""Decorate the real repository for now.
877
In the long term getting this back from the remote repository as part
878
of open would be more efficient.
881
return self._real_repository._fetch_reconcile
883
def get_parent_map(self, keys):
884
"""See bzrlib.Graph.get_parent_map()."""
885
# Hack to build up the caching logic.
886
ancestry = self._parents_map
888
# Repository is not locked, so there's no cache.
889
missing_revisions = set(keys)
892
missing_revisions = set(key for key in keys if key not in ancestry)
893
if missing_revisions:
894
parent_map = self._get_parent_map(missing_revisions)
895
if 'hpss' in debug.debug_flags:
896
mutter('retransmitted revisions: %d of %d',
897
len(set(ancestry).intersection(parent_map)),
899
ancestry.update(parent_map)
900
present_keys = [k for k in keys if k in ancestry]
901
if 'hpss' in debug.debug_flags:
902
if self._requested_parents is not None and len(ancestry) != 0:
903
self._requested_parents.update(present_keys)
904
mutter('Current RemoteRepository graph hit rate: %d%%',
905
100.0 * len(self._requested_parents) / len(ancestry))
906
return dict((k, ancestry[k]) for k in present_keys)
908
def _get_parent_map(self, keys):
909
"""Helper for get_parent_map that performs the RPC."""
910
medium = self._client._medium
911
if medium._is_remote_before((1, 2)):
912
# We already found out that the server can't understand
913
# Repository.get_parent_map requests, so just fetch the whole
915
# XXX: Note that this will issue a deprecation warning. This is ok
916
# :- its because we're working with a deprecated server anyway, and
917
# the user will almost certainly have seen a warning about the
918
# server version already.
919
rg = self.get_revision_graph()
920
# There is an api discrepency between get_parent_map and
921
# get_revision_graph. Specifically, a "key:()" pair in
922
# get_revision_graph just means a node has no parents. For
923
# "get_parent_map" it means the node is a ghost. So fix up the
924
# graph to correct this.
925
# https://bugs.launchpad.net/bzr/+bug/214894
926
# There is one other "bug" which is that ghosts in
927
# get_revision_graph() are not returned at all. But we won't worry
928
# about that for now.
929
for node_id, parent_ids in rg.iteritems():
931
rg[node_id] = (NULL_REVISION,)
932
rg[NULL_REVISION] = ()
937
raise ValueError('get_parent_map(None) is not valid')
938
if NULL_REVISION in keys:
939
keys.discard(NULL_REVISION)
940
found_parents = {NULL_REVISION:()}
945
# TODO(Needs analysis): We could assume that the keys being requested
946
# from get_parent_map are in a breadth first search, so typically they
947
# will all be depth N from some common parent, and we don't have to
948
# have the server iterate from the root parent, but rather from the
949
# keys we're searching; and just tell the server the keyspace we
950
# already have; but this may be more traffic again.
952
# Transform self._parents_map into a search request recipe.
953
# TODO: Manage this incrementally to avoid covering the same path
954
# repeatedly. (The server will have to on each request, but the less
955
# work done the better).
956
parents_map = self._parents_map
957
if parents_map is None:
958
# Repository is not locked, so there's no cache.
960
start_set = set(parents_map)
961
result_parents = set()
962
for parents in parents_map.itervalues():
963
result_parents.update(parents)
964
stop_keys = result_parents.difference(start_set)
965
included_keys = start_set.intersection(result_parents)
966
start_set.difference_update(included_keys)
967
recipe = (start_set, stop_keys, len(parents_map))
968
body = self._serialise_search_recipe(recipe)
969
path = self.bzrdir._path_for_remote_call(self._client)
971
if type(key) is not str:
973
"key %r not a plain string" % (key,))
974
verb = 'Repository.get_parent_map'
975
args = (path,) + tuple(keys)
977
response = self._call_with_body_bytes_expecting_body(
979
except errors.UnknownSmartMethod:
980
# Server does not support this method, so get the whole graph.
981
# Worse, we have to force a disconnection, because the server now
982
# doesn't realise it has a body on the wire to consume, so the
983
# only way to recover is to abandon the connection.
985
'Server is too old for fast get_parent_map, reconnecting. '
986
'(Upgrade the server to Bazaar 1.2 to avoid this)')
988
# To avoid having to disconnect repeatedly, we keep track of the
989
# fact the server doesn't understand remote methods added in 1.2.
990
medium._remember_remote_is_before((1, 2))
991
return self.get_revision_graph(None)
992
response_tuple, response_handler = response
993
if response_tuple[0] not in ['ok']:
994
response_handler.cancel_read_body()
995
raise errors.UnexpectedSmartServerResponse(response_tuple)
996
if response_tuple[0] == 'ok':
997
coded = bz2.decompress(response_handler.read_body_bytes())
1001
lines = coded.split('\n')
1004
d = tuple(line.split())
1006
revision_graph[d[0]] = d[1:]
1008
# No parents - so give the Graph result (NULL_REVISION,).
1009
revision_graph[d[0]] = (NULL_REVISION,)
1010
return revision_graph
1013
def get_signature_text(self, revision_id):
1015
return self._real_repository.get_signature_text(revision_id)
1018
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1019
def get_revision_graph_with_ghosts(self, revision_ids=None):
1021
return self._real_repository.get_revision_graph_with_ghosts(
1022
revision_ids=revision_ids)
1025
def get_inventory_xml(self, revision_id):
1027
return self._real_repository.get_inventory_xml(revision_id)
1029
def deserialise_inventory(self, revision_id, xml):
1031
return self._real_repository.deserialise_inventory(revision_id, xml)
1033
def reconcile(self, other=None, thorough=False):
1035
return self._real_repository.reconcile(other=other, thorough=thorough)
1037
def all_revision_ids(self):
1039
return self._real_repository.all_revision_ids()
1042
def get_deltas_for_revisions(self, revisions):
1044
return self._real_repository.get_deltas_for_revisions(revisions)
1047
def get_revision_delta(self, revision_id):
1049
return self._real_repository.get_revision_delta(revision_id)
1052
def revision_trees(self, revision_ids):
1054
return self._real_repository.revision_trees(revision_ids)
1057
def get_revision_reconcile(self, revision_id):
1059
return self._real_repository.get_revision_reconcile(revision_id)
1062
def check(self, revision_ids=None):
1064
return self._real_repository.check(revision_ids=revision_ids)
1066
def copy_content_into(self, destination, revision_id=None):
1068
return self._real_repository.copy_content_into(
1069
destination, revision_id=revision_id)
1071
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1072
# get a tarball of the remote repository, and copy from that into the
1074
from bzrlib import osutils
1076
# TODO: Maybe a progress bar while streaming the tarball?
1077
note("Copying repository content as tarball...")
1078
tar_file = self._get_tarball('bz2')
1079
if tar_file is None:
1081
destination = to_bzrdir.create_repository()
1083
tar = tarfile.open('repository', fileobj=tar_file,
1085
tmpdir = osutils.mkdtemp()
1087
_extract_tar(tar, tmpdir)
1088
tmp_bzrdir = BzrDir.open(tmpdir)
1089
tmp_repo = tmp_bzrdir.open_repository()
1090
tmp_repo.copy_content_into(destination, revision_id)
1092
osutils.rmtree(tmpdir)
1096
# TODO: Suggestion from john: using external tar is much faster than
1097
# python's tarfile library, but it may not work on windows.
1100
def inventories(self):
1101
"""Decorate the real repository for now.
1103
In the long term a full blown network facility is needed to
1104
avoid creating a real repository object locally.
1107
return self._real_repository.inventories
1111
"""Compress the data within the repository.
1113
This is not currently implemented within the smart server.
1116
return self._real_repository.pack()
1119
def revisions(self):
1120
"""Decorate the real repository for now.
1122
In the short term this should become a real object to intercept graph
1125
In the long term a full blown network facility is needed.
1128
return self._real_repository.revisions
1130
def set_make_working_trees(self, new_value):
1132
self._real_repository.set_make_working_trees(new_value)
1135
def signatures(self):
1136
"""Decorate the real repository for now.
1138
In the long term a full blown network facility is needed to avoid
1139
creating a real repository object locally.
1142
return self._real_repository.signatures
1145
def sign_revision(self, revision_id, gpg_strategy):
1147
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1151
"""Decorate the real repository for now.
1153
In the long term a full blown network facility is needed to avoid
1154
creating a real repository object locally.
1157
return self._real_repository.texts
1160
def get_revisions(self, revision_ids):
1162
return self._real_repository.get_revisions(revision_ids)
1164
def supports_rich_root(self):
1166
return self._real_repository.supports_rich_root()
1168
def iter_reverse_revision_history(self, revision_id):
1170
return self._real_repository.iter_reverse_revision_history(revision_id)
1173
def _serializer(self):
1175
return self._real_repository._serializer
1177
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1179
return self._real_repository.store_revision_signature(
1180
gpg_strategy, plaintext, revision_id)
1182
def add_signature_text(self, revision_id, signature):
1184
return self._real_repository.add_signature_text(revision_id, signature)
1186
def has_signature_for_revision_id(self, revision_id):
1188
return self._real_repository.has_signature_for_revision_id(revision_id)
1190
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1192
return self._real_repository.item_keys_introduced_by(revision_ids,
1193
_files_pb=_files_pb)
1195
def revision_graph_can_have_wrong_parents(self):
1196
# The answer depends on the remote repo format.
1198
return self._real_repository.revision_graph_can_have_wrong_parents()
1200
def _find_inconsistent_revision_parents(self):
1202
return self._real_repository._find_inconsistent_revision_parents()
1204
def _check_for_inconsistent_revision_parents(self):
1206
return self._real_repository._check_for_inconsistent_revision_parents()
1208
def _make_parents_provider(self):
1211
def _serialise_search_recipe(self, recipe):
1212
"""Serialise a graph search recipe.
1214
:param recipe: A search recipe (start, stop, count).
1215
:return: Serialised bytes.
1217
start_keys = ' '.join(recipe[0])
1218
stop_keys = ' '.join(recipe[1])
1219
count = str(recipe[2])
1220
return '\n'.join((start_keys, stop_keys, count))
1223
path = self.bzrdir._path_for_remote_call(self._client)
1225
response = self._call('PackRepository.autopack', path)
1226
except errors.UnknownSmartMethod:
1228
self._real_repository._pack_collection.autopack()
1230
if self._real_repository is not None:
1231
# Reset the real repository's cache of pack names.
1232
# XXX: At some point we may be able to skip this and just rely on
1233
# the automatic retry logic to do the right thing, but for now we
1234
# err on the side of being correct rather than being optimal.
1235
self._real_repository._pack_collection.reload_pack_names()
1236
if response[0] != 'ok':
1237
raise errors.UnexpectedSmartServerResponse(response)
1240
class RemoteBranchLockableFiles(LockableFiles):
1241
"""A 'LockableFiles' implementation that talks to a smart server.
1243
This is not a public interface class.
1246
def __init__(self, bzrdir, _client):
1247
self.bzrdir = bzrdir
1248
self._client = _client
1249
self._need_find_modes = True
1250
LockableFiles.__init__(
1251
self, bzrdir.get_branch_transport(None),
1252
'lock', lockdir.LockDir)
1254
def _find_modes(self):
1255
# RemoteBranches don't let the client set the mode of control files.
1256
self._dir_mode = None
1257
self._file_mode = None
1260
class RemoteBranchFormat(branch.BranchFormat):
1262
def __eq__(self, other):
1263
return (isinstance(other, RemoteBranchFormat) and
1264
self.__dict__ == other.__dict__)
1266
def get_format_description(self):
1267
return 'Remote BZR Branch'
1269
def get_format_string(self):
1270
return 'Remote BZR Branch'
1272
def open(self, a_bzrdir):
1273
return a_bzrdir.open_branch()
1275
def initialize(self, a_bzrdir):
1276
return a_bzrdir.create_branch()
1278
def supports_tags(self):
1279
# Remote branches might support tags, but we won't know until we
1280
# access the real remote branch.
1284
class RemoteBranch(branch.Branch, _RpcHelper):
1285
"""Branch stored on a server accessed by HPSS RPC.
1287
At the moment most operations are mapped down to simple file operations.
1290
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1292
"""Create a RemoteBranch instance.
1294
:param real_branch: An optional local implementation of the branch
1295
format, usually accessing the data via the VFS.
1296
:param _client: Private parameter for testing.
1298
# We intentionally don't call the parent class's __init__, because it
1299
# will try to assign to self.tags, which is a property in this subclass.
1300
# And the parent's __init__ doesn't do much anyway.
1301
self._revision_id_to_revno_cache = None
1302
self._revision_history_cache = None
1303
self._last_revision_info_cache = None
1304
self.bzrdir = remote_bzrdir
1305
if _client is not None:
1306
self._client = _client
1308
self._client = remote_bzrdir._client
1309
self.repository = remote_repository
1310
if real_branch is not None:
1311
self._real_branch = real_branch
1312
# Give the remote repository the matching real repo.
1313
real_repo = self._real_branch.repository
1314
if isinstance(real_repo, RemoteRepository):
1315
real_repo._ensure_real()
1316
real_repo = real_repo._real_repository
1317
self.repository._set_real_repository(real_repo)
1318
# Give the branch the remote repository to let fast-pathing happen.
1319
self._real_branch.repository = self.repository
1321
self._real_branch = None
1322
# Fill out expected attributes of branch for bzrlib api users.
1323
self._format = RemoteBranchFormat()
1324
self.base = self.bzrdir.root_transport.base
1325
self._control_files = None
1326
self._lock_mode = None
1327
self._lock_token = None
1328
self._repo_lock_token = None
1329
self._lock_count = 0
1330
self._leave_lock = False
1331
# The base class init is not called, so we duplicate this:
1332
hooks = branch.Branch.hooks['open']
1335
self._setup_stacking()
1337
def _setup_stacking(self):
1338
# configure stacking into the remote repository, by reading it from
1341
fallback_url = self.get_stacked_on_url()
1342
except (errors.NotStacked, errors.UnstackableBranchFormat,
1343
errors.UnstackableRepositoryFormat), e:
1345
# it's relative to this branch...
1346
fallback_url = urlutils.join(self.base, fallback_url)
1347
transports = [self.bzrdir.root_transport]
1348
if self._real_branch is not None:
1349
transports.append(self._real_branch._transport)
1350
stacked_on = branch.Branch.open(fallback_url,
1351
possible_transports=transports)
1352
self.repository.add_fallback_repository(stacked_on.repository)
1354
def _get_real_transport(self):
1355
# if we try vfs access, return the real branch's vfs transport
1357
return self._real_branch._transport
1359
_transport = property(_get_real_transport)
1362
return "%s(%s)" % (self.__class__.__name__, self.base)
1366
def _ensure_real(self):
1367
"""Ensure that there is a _real_branch set.
1369
Used before calls to self._real_branch.
1371
if self._real_branch is None:
1372
if not vfs.vfs_enabled():
1373
raise AssertionError('smart server vfs must be enabled '
1374
'to use vfs implementation')
1375
self.bzrdir._ensure_real()
1376
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1377
if self.repository._real_repository is None:
1378
# Give the remote repository the matching real repo.
1379
real_repo = self._real_branch.repository
1380
if isinstance(real_repo, RemoteRepository):
1381
real_repo._ensure_real()
1382
real_repo = real_repo._real_repository
1383
self.repository._set_real_repository(real_repo)
1384
# Give the real branch the remote repository to let fast-pathing
1386
self._real_branch.repository = self.repository
1387
if self._lock_mode == 'r':
1388
self._real_branch.lock_read()
1389
elif self._lock_mode == 'w':
1390
self._real_branch.lock_write(token=self._lock_token)
1392
def _translate_error(self, err, **context):
1393
self.repository._translate_error(err, branch=self, **context)
1395
def _clear_cached_state(self):
1396
super(RemoteBranch, self)._clear_cached_state()
1397
if self._real_branch is not None:
1398
self._real_branch._clear_cached_state()
1400
def _clear_cached_state_of_remote_branch_only(self):
1401
"""Like _clear_cached_state, but doesn't clear the cache of
1404
This is useful when falling back to calling a method of
1405
self._real_branch that changes state. In that case the underlying
1406
branch changes, so we need to invalidate this RemoteBranch's cache of
1407
it. However, there's no need to invalidate the _real_branch's cache
1408
too, in fact doing so might harm performance.
1410
super(RemoteBranch, self)._clear_cached_state()
1413
def control_files(self):
1414
# Defer actually creating RemoteBranchLockableFiles until its needed,
1415
# because it triggers an _ensure_real that we otherwise might not need.
1416
if self._control_files is None:
1417
self._control_files = RemoteBranchLockableFiles(
1418
self.bzrdir, self._client)
1419
return self._control_files
1421
def _get_checkout_format(self):
1423
return self._real_branch._get_checkout_format()
1425
def get_physical_lock_status(self):
1426
"""See Branch.get_physical_lock_status()."""
1427
# should be an API call to the server, as branches must be lockable.
1429
return self._real_branch.get_physical_lock_status()
1431
def get_stacked_on_url(self):
1432
"""Get the URL this branch is stacked against.
1434
:raises NotStacked: If the branch is not stacked.
1435
:raises UnstackableBranchFormat: If the branch does not support
1437
:raises UnstackableRepositoryFormat: If the repository does not support
1441
# there may not be a repository yet, so we can't use
1442
# self._translate_error, so we can't use self._call either.
1443
response = self._client.call('Branch.get_stacked_on_url',
1444
self._remote_path())
1445
except errors.ErrorFromSmartServer, err:
1446
# there may not be a repository yet, so we can't call through
1447
# its _translate_error
1448
_translate_error(err, branch=self)
1449
except errors.UnknownSmartMethod, err:
1451
return self._real_branch.get_stacked_on_url()
1452
if response[0] != 'ok':
1453
raise errors.UnexpectedSmartServerResponse(response)
1456
def lock_read(self):
1457
self.repository.lock_read()
1458
if not self._lock_mode:
1459
self._lock_mode = 'r'
1460
self._lock_count = 1
1461
if self._real_branch is not None:
1462
self._real_branch.lock_read()
1464
self._lock_count += 1
1466
def _remote_lock_write(self, token):
1468
branch_token = repo_token = ''
1470
branch_token = token
1471
repo_token = self.repository.lock_write()
1472
self.repository.unlock()
1473
err_context = {'token': token}
1474
response = self._call(
1475
'Branch.lock_write', self._remote_path(), branch_token,
1476
repo_token or '', **err_context)
1477
if response[0] != 'ok':
1478
raise errors.UnexpectedSmartServerResponse(response)
1479
ok, branch_token, repo_token = response
1480
return branch_token, repo_token
1482
def lock_write(self, token=None):
1483
if not self._lock_mode:
1484
# Lock the branch and repo in one remote call.
1485
remote_tokens = self._remote_lock_write(token)
1486
self._lock_token, self._repo_lock_token = remote_tokens
1487
if not self._lock_token:
1488
raise SmartProtocolError('Remote server did not return a token!')
1489
# Tell the self.repository object that it is locked.
1490
self.repository.lock_write(
1491
self._repo_lock_token, _skip_rpc=True)
1493
if self._real_branch is not None:
1494
self._real_branch.lock_write(token=self._lock_token)
1495
if token is not None:
1496
self._leave_lock = True
1498
self._leave_lock = False
1499
self._lock_mode = 'w'
1500
self._lock_count = 1
1501
elif self._lock_mode == 'r':
1502
raise errors.ReadOnlyTransaction
1504
if token is not None:
1505
# A token was given to lock_write, and we're relocking, so
1506
# check that the given token actually matches the one we
1508
if token != self._lock_token:
1509
raise errors.TokenMismatch(token, self._lock_token)
1510
self._lock_count += 1
1511
# Re-lock the repository too.
1512
self.repository.lock_write(self._repo_lock_token)
1513
return self._lock_token or None
1515
def _unlock(self, branch_token, repo_token):
1516
err_context = {'token': str((branch_token, repo_token))}
1517
response = self._call(
1518
'Branch.unlock', self._remote_path(), branch_token,
1519
repo_token or '', **err_context)
1520
if response == ('ok',):
1522
raise errors.UnexpectedSmartServerResponse(response)
1526
self._lock_count -= 1
1527
if not self._lock_count:
1528
self._clear_cached_state()
1529
mode = self._lock_mode
1530
self._lock_mode = None
1531
if self._real_branch is not None:
1532
if (not self._leave_lock and mode == 'w' and
1533
self._repo_lock_token):
1534
# If this RemoteBranch will remove the physical lock
1535
# for the repository, make sure the _real_branch
1536
# doesn't do it first. (Because the _real_branch's
1537
# repository is set to be the RemoteRepository.)
1538
self._real_branch.repository.leave_lock_in_place()
1539
self._real_branch.unlock()
1541
# Only write-locked branched need to make a remote method
1542
# call to perfom the unlock.
1544
if not self._lock_token:
1545
raise AssertionError('Locked, but no token!')
1546
branch_token = self._lock_token
1547
repo_token = self._repo_lock_token
1548
self._lock_token = None
1549
self._repo_lock_token = None
1550
if not self._leave_lock:
1551
self._unlock(branch_token, repo_token)
1553
self.repository.unlock()
1555
def break_lock(self):
1557
return self._real_branch.break_lock()
1559
def leave_lock_in_place(self):
1560
if not self._lock_token:
1561
raise NotImplementedError(self.leave_lock_in_place)
1562
self._leave_lock = True
1564
def dont_leave_lock_in_place(self):
1565
if not self._lock_token:
1566
raise NotImplementedError(self.dont_leave_lock_in_place)
1567
self._leave_lock = False
1569
def _last_revision_info(self):
1570
response = self._call('Branch.last_revision_info', self._remote_path())
1571
if response[0] != 'ok':
1572
raise SmartProtocolError('unexpected response code %s' % (response,))
1573
revno = int(response[1])
1574
last_revision = response[2]
1575
return (revno, last_revision)
1577
def _gen_revision_history(self):
1578
"""See Branch._gen_revision_history()."""
1579
response_tuple, response_handler = self._call_expecting_body(
1580
'Branch.revision_history', self._remote_path())
1581
if response_tuple[0] != 'ok':
1582
raise errors.UnexpectedSmartServerResponse(response_tuple)
1583
result = response_handler.read_body_bytes().split('\x00')
1588
def _remote_path(self):
1589
return self.bzrdir._path_for_remote_call(self._client)
1591
def _set_last_revision_descendant(self, revision_id, other_branch,
1592
allow_diverged=False, allow_overwrite_descendant=False):
1593
err_context = {'other_branch': other_branch}
1594
response = self._call('Branch.set_last_revision_ex',
1595
self._remote_path(), self._lock_token, self._repo_lock_token,
1596
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1598
self._clear_cached_state()
1599
if len(response) != 3 and response[0] != 'ok':
1600
raise errors.UnexpectedSmartServerResponse(response)
1601
new_revno, new_revision_id = response[1:]
1602
self._last_revision_info_cache = new_revno, new_revision_id
1603
if self._real_branch is not None:
1604
cache = new_revno, new_revision_id
1605
self._real_branch._last_revision_info_cache = cache
1607
def _set_last_revision(self, revision_id):
1608
self._clear_cached_state()
1609
response = self._call('Branch.set_last_revision',
1610
self._remote_path(), self._lock_token, self._repo_lock_token,
1612
if response != ('ok',):
1613
raise errors.UnexpectedSmartServerResponse(response)
1616
def set_revision_history(self, rev_history):
1617
# Send just the tip revision of the history; the server will generate
1618
# the full history from that. If the revision doesn't exist in this
1619
# branch, NoSuchRevision will be raised.
1620
if rev_history == []:
1623
rev_id = rev_history[-1]
1624
self._set_last_revision(rev_id)
1625
self._cache_revision_history(rev_history)
1627
def get_parent(self):
1629
return self._real_branch.get_parent()
1631
def set_parent(self, url):
1633
return self._real_branch.set_parent(url)
1635
def set_stacked_on_url(self, stacked_location):
1636
"""Set the URL this branch is stacked against.
1638
:raises UnstackableBranchFormat: If the branch does not support
1640
:raises UnstackableRepositoryFormat: If the repository does not support
1644
return self._real_branch.set_stacked_on_url(stacked_location)
1646
def sprout(self, to_bzrdir, revision_id=None):
1647
branch_format = to_bzrdir._format._branch_format
1648
if (branch_format is None or
1649
isinstance(branch_format, RemoteBranchFormat)):
1650
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1651
# implies the same thing), but RemoteBranches can't be created at
1652
# arbitrary URLs. So create a branch in the same format as
1653
# _real_branch instead.
1654
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1655
# to_bzrdir.create_branch to create a RemoteBranch after all...
1657
result = self._real_branch._format.initialize(to_bzrdir)
1658
self.copy_content_into(result, revision_id=revision_id)
1659
result.set_parent(self.bzrdir.root_transport.base)
1661
result = branch.Branch.sprout(
1662
self, to_bzrdir, revision_id=revision_id)
1666
def pull(self, source, overwrite=False, stop_revision=None,
1668
self._clear_cached_state_of_remote_branch_only()
1670
return self._real_branch.pull(
1671
source, overwrite=overwrite, stop_revision=stop_revision,
1672
_override_hook_target=self, **kwargs)
1675
def push(self, target, overwrite=False, stop_revision=None):
1677
return self._real_branch.push(
1678
target, overwrite=overwrite, stop_revision=stop_revision,
1679
_override_hook_source_branch=self)
1681
def is_locked(self):
1682
return self._lock_count >= 1
1685
def revision_id_to_revno(self, revision_id):
1687
return self._real_branch.revision_id_to_revno(revision_id)
1690
def set_last_revision_info(self, revno, revision_id):
1691
revision_id = ensure_null(revision_id)
1693
response = self._call('Branch.set_last_revision_info',
1694
self._remote_path(), self._lock_token, self._repo_lock_token,
1695
str(revno), revision_id)
1696
except errors.UnknownSmartMethod:
1698
self._clear_cached_state_of_remote_branch_only()
1699
self._real_branch.set_last_revision_info(revno, revision_id)
1700
self._last_revision_info_cache = revno, revision_id
1702
if response == ('ok',):
1703
self._clear_cached_state()
1704
self._last_revision_info_cache = revno, revision_id
1705
# Update the _real_branch's cache too.
1706
if self._real_branch is not None:
1707
cache = self._last_revision_info_cache
1708
self._real_branch._last_revision_info_cache = cache
1710
raise errors.UnexpectedSmartServerResponse(response)
1713
def generate_revision_history(self, revision_id, last_rev=None,
1715
medium = self._client._medium
1716
if not medium._is_remote_before((1, 6)):
1718
self._set_last_revision_descendant(revision_id, other_branch,
1719
allow_diverged=True, allow_overwrite_descendant=True)
1721
except errors.UnknownSmartMethod:
1722
medium._remember_remote_is_before((1, 6))
1723
self._clear_cached_state_of_remote_branch_only()
1725
self._real_branch.generate_revision_history(
1726
revision_id, last_rev=last_rev, other_branch=other_branch)
1731
return self._real_branch.tags
1733
def set_push_location(self, location):
1735
return self._real_branch.set_push_location(location)
1738
def update_revisions(self, other, stop_revision=None, overwrite=False,
1740
"""See Branch.update_revisions."""
1743
if stop_revision is None:
1744
stop_revision = other.last_revision()
1745
if revision.is_null(stop_revision):
1746
# if there are no commits, we're done.
1748
self.fetch(other, stop_revision)
1751
# Just unconditionally set the new revision. We don't care if
1752
# the branches have diverged.
1753
self._set_last_revision(stop_revision)
1755
medium = self._client._medium
1756
if not medium._is_remote_before((1, 6)):
1758
self._set_last_revision_descendant(stop_revision, other)
1760
except errors.UnknownSmartMethod:
1761
medium._remember_remote_is_before((1, 6))
1762
# Fallback for pre-1.6 servers: check for divergence
1763
# client-side, then do _set_last_revision.
1764
last_rev = revision.ensure_null(self.last_revision())
1766
graph = self.repository.get_graph()
1767
if self._check_if_descendant_or_diverged(
1768
stop_revision, last_rev, graph, other):
1769
# stop_revision is a descendant of last_rev, but we aren't
1770
# overwriting, so we're done.
1772
self._set_last_revision(stop_revision)
1777
def _extract_tar(tar, to_dir):
1778
"""Extract all the contents of a tarfile object.
1780
A replacement for extractall, which is not present in python2.4
1783
tar.extract(tarinfo, to_dir)
1786
def _translate_error(err, **context):
1787
"""Translate an ErrorFromSmartServer into a more useful error.
1789
Possible context keys:
1797
If the error from the server doesn't match a known pattern, then
1798
UnknownErrorFromSmartServer is raised.
1802
return context[name]
1803
except KeyError, key_err:
1804
mutter('Missing key %r in context %r', key_err.args[0], context)
1807
"""Get the path from the context if present, otherwise use first error
1811
return context['path']
1812
except KeyError, key_err:
1814
return err.error_args[0]
1815
except IndexError, idx_err:
1817
'Missing key %r in context %r', key_err.args[0], context)
1820
if err.error_verb == 'NoSuchRevision':
1821
raise NoSuchRevision(find('branch'), err.error_args[0])
1822
elif err.error_verb == 'nosuchrevision':
1823
raise NoSuchRevision(find('repository'), err.error_args[0])
1824
elif err.error_tuple == ('nobranch',):
1825
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1826
elif err.error_verb == 'norepository':
1827
raise errors.NoRepositoryPresent(find('bzrdir'))
1828
elif err.error_verb == 'LockContention':
1829
raise errors.LockContention('(remote lock)')
1830
elif err.error_verb == 'UnlockableTransport':
1831
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1832
elif err.error_verb == 'LockFailed':
1833
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1834
elif err.error_verb == 'TokenMismatch':
1835
raise errors.TokenMismatch(find('token'), '(remote token)')
1836
elif err.error_verb == 'Diverged':
1837
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1838
elif err.error_verb == 'TipChangeRejected':
1839
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1840
elif err.error_verb == 'UnstackableBranchFormat':
1841
raise errors.UnstackableBranchFormat(*err.error_args)
1842
elif err.error_verb == 'UnstackableRepositoryFormat':
1843
raise errors.UnstackableRepositoryFormat(*err.error_args)
1844
elif err.error_verb == 'NotStacked':
1845
raise errors.NotStacked(branch=find('branch'))
1846
elif err.error_verb == 'PermissionDenied':
1848
if len(err.error_args) >= 2:
1849
extra = err.error_args[1]
1852
raise errors.PermissionDenied(path, extra=extra)
1853
elif err.error_verb == 'ReadError':
1855
raise errors.ReadError(path)
1856
elif err.error_verb == 'NoSuchFile':
1858
raise errors.NoSuchFile(path)
1859
elif err.error_verb == 'FileExists':
1860
raise errors.FileExists(err.error_args[0])
1861
elif err.error_verb == 'DirectoryNotEmpty':
1862
raise errors.DirectoryNotEmpty(err.error_args[0])
1863
elif err.error_verb == 'ShortReadvError':
1864
args = err.error_args
1865
raise errors.ShortReadvError(
1866
args[0], int(args[1]), int(args[2]), int(args[3]))
1867
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1868
encoding = str(err.error_args[0]) # encoding must always be a string
1869
val = err.error_args[1]
1870
start = int(err.error_args[2])
1871
end = int(err.error_args[3])
1872
reason = str(err.error_args[4]) # reason must always be a string
1873
if val.startswith('u:'):
1874
val = val[2:].decode('utf-8')
1875
elif val.startswith('s:'):
1876
val = val[2:].decode('base64')
1877
if err.error_verb == 'UnicodeDecodeError':
1878
raise UnicodeDecodeError(encoding, val, start, end, reason)
1879
elif err.error_verb == 'UnicodeEncodeError':
1880
raise UnicodeEncodeError(encoding, val, start, end, reason)
1881
elif err.error_verb == 'ReadOnlyError':
1882
raise errors.TransportNotPossible('readonly transport')
1883
raise errors.UnknownErrorFromSmartServer(err)