1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
45
from bzrlib.util import bencode
46
from bzrlib.versionedfile import VersionedFiles
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
# Note: RemoteBzrDirFormat is in bzrdir.py
74
class RemoteBzrDir(BzrDir, _RpcHelper):
75
"""Control directory on a remote server, accessed via bzr:// or similar."""
77
def __init__(self, transport, _client=None):
78
"""Construct a RemoteBzrDir.
80
:param _client: Private parameter for testing. Disables probing and the
83
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
84
# this object holds a delegated bzrdir that uses file-level operations
85
# to talk to the other side
86
self._real_bzrdir = None
89
medium = transport.get_smart_medium()
90
self._client = client._SmartClient(medium)
92
self._client = _client
95
path = self._path_for_remote_call(self._client)
96
response = self._call('BzrDir.open', path)
97
if response not in [('yes',), ('no',)]:
98
raise errors.UnexpectedSmartServerResponse(response)
99
if response == ('no',):
100
raise errors.NotBranchError(path=transport.base)
102
def _ensure_real(self):
103
"""Ensure that there is a _real_bzrdir set.
105
Used before calls to self._real_bzrdir.
107
if not self._real_bzrdir:
108
self._real_bzrdir = BzrDir.open_from_transport(
109
self.root_transport, _server_formats=False)
111
def _translate_error(self, err, **context):
112
_translate_error(err, bzrdir=self, **context)
114
def cloning_metadir(self, stacked=False):
116
return self._real_bzrdir.cloning_metadir(stacked)
118
def create_repository(self, shared=False):
120
self._real_bzrdir.create_repository(shared=shared)
121
return self.open_repository()
123
def destroy_repository(self):
124
"""See BzrDir.destroy_repository"""
126
self._real_bzrdir.destroy_repository()
128
def create_branch(self):
130
real_branch = self._real_bzrdir.create_branch()
131
return RemoteBranch(self, self.find_repository(), real_branch)
133
def destroy_branch(self):
134
"""See BzrDir.destroy_branch"""
136
self._real_bzrdir.destroy_branch()
138
def create_workingtree(self, revision_id=None, from_branch=None):
139
raise errors.NotLocalUrl(self.transport.base)
141
def find_branch_format(self):
142
"""Find the branch 'format' for this bzrdir.
144
This might be a synthetic object for e.g. RemoteBranch and SVN.
146
b = self.open_branch()
149
def get_branch_reference(self):
150
"""See BzrDir.get_branch_reference()."""
151
path = self._path_for_remote_call(self._client)
152
response = self._call('BzrDir.open_branch', path)
153
if response[0] == 'ok':
154
if response[1] == '':
155
# branch at this location.
158
# a branch reference, use the existing BranchReference logic.
161
raise errors.UnexpectedSmartServerResponse(response)
163
def _get_tree_branch(self):
164
"""See BzrDir._get_tree_branch()."""
165
return None, self.open_branch()
167
def open_branch(self, _unsupported=False):
169
raise NotImplementedError('unsupported flag support not implemented yet.')
170
reference_url = self.get_branch_reference()
171
if reference_url is None:
172
# branch at this location.
173
return RemoteBranch(self, self.find_repository())
175
# a branch reference, use the existing BranchReference logic.
176
format = BranchReferenceFormat()
177
return format.open(self, _found=True, location=reference_url)
179
def open_repository(self):
180
path = self._path_for_remote_call(self._client)
181
verb = 'BzrDir.find_repositoryV2'
183
response = self._call(verb, path)
184
except errors.UnknownSmartMethod:
185
verb = 'BzrDir.find_repository'
186
response = self._call(verb, path)
187
if response[0] != 'ok':
188
raise errors.UnexpectedSmartServerResponse(response)
189
if verb == 'BzrDir.find_repository':
190
# servers that don't support the V2 method don't support external
192
response = response + ('no', )
193
if not (len(response) == 5):
194
raise SmartProtocolError('incorrect response length %s' % (response,))
195
if response[1] == '':
196
format = RemoteRepositoryFormat()
197
format.rich_root_data = (response[2] == 'yes')
198
format.supports_tree_reference = (response[3] == 'yes')
199
# No wire format to check this yet.
200
format.supports_external_lookups = (response[4] == 'yes')
201
# Used to support creating a real format instance when needed.
202
format._creating_bzrdir = self
203
return RemoteRepository(self, format)
205
raise errors.NoRepositoryPresent(self)
207
def open_workingtree(self, recommend_upgrade=True):
209
if self._real_bzrdir.has_workingtree():
210
raise errors.NotLocalUrl(self.root_transport)
212
raise errors.NoWorkingTree(self.root_transport.base)
214
def _path_for_remote_call(self, client):
215
"""Return the path to be used for this bzrdir in a remote call."""
216
return client.remote_path_from_transport(self.root_transport)
218
def get_branch_transport(self, branch_format):
220
return self._real_bzrdir.get_branch_transport(branch_format)
222
def get_repository_transport(self, repository_format):
224
return self._real_bzrdir.get_repository_transport(repository_format)
226
def get_workingtree_transport(self, workingtree_format):
228
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
230
def can_convert_format(self):
231
"""Upgrading of remote bzrdirs is not supported yet."""
234
def needs_format_conversion(self, format=None):
235
"""Upgrading of remote bzrdirs is not supported yet."""
238
def clone(self, url, revision_id=None, force_new_repo=False,
239
preserve_stacking=False):
241
return self._real_bzrdir.clone(url, revision_id=revision_id,
242
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
244
def get_config(self):
246
return self._real_bzrdir.get_config()
249
class RemoteRepositoryFormat(repository.RepositoryFormat):
250
"""Format for repositories accessed over a _SmartClient.
252
Instances of this repository are represented by RemoteRepository
255
The RemoteRepositoryFormat is parameterized during construction
256
to reflect the capabilities of the real, remote format. Specifically
257
the attributes rich_root_data and supports_tree_reference are set
258
on a per instance basis, and are not set (and should not be) at
262
_matchingbzrdir = RemoteBzrDirFormat()
264
def initialize(self, a_bzrdir, shared=False):
265
if not isinstance(a_bzrdir, RemoteBzrDir):
266
prior_repo = self._creating_bzrdir.open_repository()
267
prior_repo._ensure_real()
268
return prior_repo._real_repository._format.initialize(
269
a_bzrdir, shared=shared)
270
return a_bzrdir.create_repository(shared=shared)
272
def open(self, a_bzrdir):
273
if not isinstance(a_bzrdir, RemoteBzrDir):
274
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
275
return a_bzrdir.open_repository()
277
def get_format_description(self):
278
return 'bzr remote repository'
280
def __eq__(self, other):
281
return self.__class__ == other.__class__
283
def check_conversion_target(self, target_format):
284
if self.rich_root_data and not target_format.rich_root_data:
285
raise errors.BadConversionTarget(
286
'Does not support rich root data.', target_format)
287
if (self.supports_tree_reference and
288
not getattr(target_format, 'supports_tree_reference', False)):
289
raise errors.BadConversionTarget(
290
'Does not support nested trees', target_format)
293
class RemoteRepository(_RpcHelper):
294
"""Repository accessed over rpc.
296
For the moment most operations are performed using local transport-backed
300
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
301
"""Create a RemoteRepository instance.
303
:param remote_bzrdir: The bzrdir hosting this repository.
304
:param format: The RemoteFormat object to use.
305
:param real_repository: If not None, a local implementation of the
306
repository logic for the repository, usually accessing the data
308
:param _client: Private testing parameter - override the smart client
309
to be used by the repository.
312
self._real_repository = real_repository
314
self._real_repository = None
315
self.bzrdir = remote_bzrdir
317
self._client = remote_bzrdir._client
319
self._client = _client
320
self._format = format
321
self._lock_mode = None
322
self._lock_token = None
324
self._leave_lock = False
325
self._unstacked_provider = graph.CachingParentsProvider(
326
get_parent_map=self._get_parent_map_rpc)
327
self._unstacked_provider.disable_cache()
329
# These depend on the actual remote format, so force them off for
330
# maximum compatibility. XXX: In future these should depend on the
331
# remote repository instance, but this is irrelevant until we perform
332
# reconcile via an RPC call.
333
self._reconcile_does_inventory_gc = False
334
self._reconcile_fixes_text_parents = False
335
self._reconcile_backsup_inventory = False
336
self.base = self.bzrdir.transport.base
337
# Additional places to query for data.
338
self._fallback_repositories = []
339
self.texts = RemoteVersionedFiles(self, 'texts')
340
self.inventories = RemoteVersionedFiles(self, 'inventories')
341
self.signatures = RemoteVersionedFiles(self, 'signatures')
342
self.revisions = RemoteVersionedFiles(self, 'revisions')
344
self.texts, self.inventories, self.signatures, self.revisions]
347
return "%s(%s)" % (self.__class__.__name__, self.base)
351
def abort_write_group(self, suppress_errors=False):
352
"""Complete a write group on the decorated repository.
354
Smart methods peform operations in a single step so this api
355
is not really applicable except as a compatibility thunk
356
for older plugins that don't use e.g. the CommitBuilder
359
:param suppress_errors: see Repository.abort_write_group.
362
return self._real_repository.abort_write_group(
363
suppress_errors=suppress_errors)
365
def commit_write_group(self):
366
"""Complete a write group on the decorated repository.
368
Smart methods peform operations in a single step so this api
369
is not really applicable except as a compatibility thunk
370
for older plugins that don't use e.g. the CommitBuilder
374
return self._real_repository.commit_write_group()
376
def _ensure_real(self):
377
"""Ensure that there is a _real_repository set.
379
Used before calls to self._real_repository.
381
if self._real_repository is None:
382
self.bzrdir._ensure_real()
383
self._set_real_repository(
384
self.bzrdir._real_bzrdir.open_repository())
386
def _translate_error(self, err, **context):
387
self.bzrdir._translate_error(err, repository=self, **context)
389
def find_text_key_references(self):
390
"""Find the text key references within the repository.
392
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
393
revision_ids. Each altered file-ids has the exact revision_ids that
394
altered it listed explicitly.
395
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
396
to whether they were referred to by the inventory of the
397
revision_id that they contain. The inventory texts from all present
398
revision ids are assessed to generate this report.
401
return self._real_repository.find_text_key_references()
403
def _generate_text_key_index(self):
404
"""Generate a new text key index for the repository.
406
This is an expensive function that will take considerable time to run.
408
:return: A dict mapping (file_id, revision_id) tuples to a list of
409
parents, also (file_id, revision_id) tuples.
412
return self._real_repository._generate_text_key_index()
414
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
415
def get_revision_graph(self, revision_id=None):
416
"""See Repository.get_revision_graph()."""
417
return self._get_revision_graph(revision_id)
419
def _get_revision_graph(self, revision_id):
420
"""Private method for using with old (< 1.2) servers to fallback."""
421
if revision_id is None:
423
elif revision.is_null(revision_id):
426
path = self.bzrdir._path_for_remote_call(self._client)
427
response = self._call_expecting_body(
428
'Repository.get_revision_graph', path, revision_id)
429
response_tuple, response_handler = response
430
if response_tuple[0] != 'ok':
431
raise errors.UnexpectedSmartServerResponse(response_tuple)
432
coded = response_handler.read_body_bytes()
434
# no revisions in this repository!
436
lines = coded.split('\n')
439
d = tuple(line.split())
440
revision_graph[d[0]] = d[1:]
442
return revision_graph
444
def has_revision(self, revision_id):
445
"""See Repository.has_revision()."""
446
if revision_id == NULL_REVISION:
447
# The null revision is always present.
449
path = self.bzrdir._path_for_remote_call(self._client)
450
response = self._call('Repository.has_revision', path, revision_id)
451
if response[0] not in ('yes', 'no'):
452
raise errors.UnexpectedSmartServerResponse(response)
453
if response[0] == 'yes':
455
for fallback_repo in self._fallback_repositories:
456
if fallback_repo.has_revision(revision_id):
460
def has_revisions(self, revision_ids):
461
"""See Repository.has_revisions()."""
462
# FIXME: This does many roundtrips, particularly when there are
463
# fallback repositories. -- mbp 20080905
465
for revision_id in revision_ids:
466
if self.has_revision(revision_id):
467
result.add(revision_id)
470
def has_same_location(self, other):
471
return (self.__class__ == other.__class__ and
472
self.bzrdir.transport.base == other.bzrdir.transport.base)
474
def get_graph(self, other_repository=None):
475
"""Return the graph for this repository format"""
476
parents_provider = self._make_parents_provider(other_repository)
477
return graph.Graph(parents_provider)
479
def gather_stats(self, revid=None, committers=None):
480
"""See Repository.gather_stats()."""
481
path = self.bzrdir._path_for_remote_call(self._client)
482
# revid can be None to indicate no revisions, not just NULL_REVISION
483
if revid is None or revision.is_null(revid):
487
if committers is None or not committers:
488
fmt_committers = 'no'
490
fmt_committers = 'yes'
491
response_tuple, response_handler = self._call_expecting_body(
492
'Repository.gather_stats', path, fmt_revid, fmt_committers)
493
if response_tuple[0] != 'ok':
494
raise errors.UnexpectedSmartServerResponse(response_tuple)
496
body = response_handler.read_body_bytes()
498
for line in body.split('\n'):
501
key, val_text = line.split(':')
502
if key in ('revisions', 'size', 'committers'):
503
result[key] = int(val_text)
504
elif key in ('firstrev', 'latestrev'):
505
values = val_text.split(' ')[1:]
506
result[key] = (float(values[0]), long(values[1]))
510
def find_branches(self, using=False):
511
"""See Repository.find_branches()."""
512
# should be an API call to the server.
514
return self._real_repository.find_branches(using=using)
516
def get_physical_lock_status(self):
517
"""See Repository.get_physical_lock_status()."""
518
# should be an API call to the server.
520
return self._real_repository.get_physical_lock_status()
522
def is_in_write_group(self):
523
"""Return True if there is an open write group.
525
write groups are only applicable locally for the smart server..
527
if self._real_repository:
528
return self._real_repository.is_in_write_group()
531
return self._lock_count >= 1
534
"""See Repository.is_shared()."""
535
path = self.bzrdir._path_for_remote_call(self._client)
536
response = self._call('Repository.is_shared', path)
537
if response[0] not in ('yes', 'no'):
538
raise SmartProtocolError('unexpected response code %s' % (response,))
539
return response[0] == 'yes'
541
def is_write_locked(self):
542
return self._lock_mode == 'w'
545
# wrong eventually - want a local lock cache context
546
if not self._lock_mode:
547
self._lock_mode = 'r'
549
self._unstacked_provider.enable_cache(cache_misses=False)
550
if self._real_repository is not None:
551
self._real_repository.lock_read()
553
self._lock_count += 1
555
def _remote_lock_write(self, token):
556
path = self.bzrdir._path_for_remote_call(self._client)
559
err_context = {'token': token}
560
response = self._call('Repository.lock_write', path, token,
562
if response[0] == 'ok':
566
raise errors.UnexpectedSmartServerResponse(response)
568
def lock_write(self, token=None, _skip_rpc=False):
569
if not self._lock_mode:
571
if self._lock_token is not None:
572
if token != self._lock_token:
573
raise errors.TokenMismatch(token, self._lock_token)
574
self._lock_token = token
576
self._lock_token = self._remote_lock_write(token)
577
# if self._lock_token is None, then this is something like packs or
578
# svn where we don't get to lock the repo, or a weave style repository
579
# where we cannot lock it over the wire and attempts to do so will
581
if self._real_repository is not None:
582
self._real_repository.lock_write(token=self._lock_token)
583
if token is not None:
584
self._leave_lock = True
586
self._leave_lock = False
587
self._lock_mode = 'w'
589
self._unstacked_provider.enable_cache(cache_misses=False)
590
elif self._lock_mode == 'r':
591
raise errors.ReadOnlyError(self)
593
self._lock_count += 1
594
return self._lock_token or None
596
def leave_lock_in_place(self):
597
if not self._lock_token:
598
raise NotImplementedError(self.leave_lock_in_place)
599
self._leave_lock = True
601
def dont_leave_lock_in_place(self):
602
if not self._lock_token:
603
raise NotImplementedError(self.dont_leave_lock_in_place)
604
self._leave_lock = False
606
def _set_real_repository(self, repository):
607
"""Set the _real_repository for this repository.
609
:param repository: The repository to fallback to for non-hpss
610
implemented operations.
612
if self._real_repository is not None:
613
raise AssertionError('_real_repository is already set')
614
if isinstance(repository, RemoteRepository):
615
raise AssertionError()
616
self._real_repository = repository
617
for fb in self._fallback_repositories:
618
self._real_repository.add_fallback_repository(fb)
619
if self._lock_mode == 'w':
620
# if we are already locked, the real repository must be able to
621
# acquire the lock with our token.
622
self._real_repository.lock_write(self._lock_token)
623
elif self._lock_mode == 'r':
624
self._real_repository.lock_read()
626
def start_write_group(self):
627
"""Start a write group on the decorated repository.
629
Smart methods peform operations in a single step so this api
630
is not really applicable except as a compatibility thunk
631
for older plugins that don't use e.g. the CommitBuilder
635
return self._real_repository.start_write_group()
637
def _unlock(self, token):
638
path = self.bzrdir._path_for_remote_call(self._client)
640
# with no token the remote repository is not persistently locked.
642
err_context = {'token': token}
643
response = self._call('Repository.unlock', path, token,
645
if response == ('ok',):
648
raise errors.UnexpectedSmartServerResponse(response)
651
self._lock_count -= 1
652
if self._lock_count > 0:
654
self._unstacked_provider.disable_cache()
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_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
758
return self._real_repository.add_inventory_by_delta(basis_revision_id,
759
delta, new_revision_id, parents)
761
def add_revision(self, rev_id, rev, inv=None, config=None):
763
return self._real_repository.add_revision(
764
rev_id, rev, inv=inv, config=config)
767
def get_inventory(self, revision_id):
769
return self._real_repository.get_inventory(revision_id)
771
def iter_inventories(self, revision_ids):
773
return self._real_repository.iter_inventories(revision_ids)
776
def get_revision(self, revision_id):
778
return self._real_repository.get_revision(revision_id)
780
def get_transaction(self):
782
return self._real_repository.get_transaction()
785
def clone(self, a_bzrdir, revision_id=None):
787
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
789
def make_working_trees(self):
790
"""See Repository.make_working_trees"""
792
return self._real_repository.make_working_trees()
794
def revision_ids_to_search_result(self, result_set):
795
"""Convert a set of revision ids to a graph SearchResult."""
796
result_parents = set()
797
for parents in self.get_graph().get_parent_map(
798
result_set).itervalues():
799
result_parents.update(parents)
800
included_keys = result_set.intersection(result_parents)
801
start_keys = result_set.difference(included_keys)
802
exclude_keys = result_parents.difference(result_set)
803
result = graph.SearchResult(start_keys, exclude_keys,
804
len(result_set), result_set)
808
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
809
"""Return the revision ids that other has that this does not.
811
These are returned in topological order.
813
revision_id: only return revision ids included by revision_id.
815
return repository.InterRepository.get(
816
other, self).search_missing_revision_ids(revision_id, find_ghosts)
818
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
819
# Not delegated to _real_repository so that InterRepository.get has a
820
# chance to find an InterRepository specialised for RemoteRepository.
821
if self.has_same_location(source):
822
# check that last_revision is in 'from' and then return a
824
if (revision_id is not None and
825
not revision.is_null(revision_id)):
826
self.get_revision(revision_id)
828
inter = repository.InterRepository.get(source, self)
830
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
831
except NotImplementedError:
832
raise errors.IncompatibleRepositories(source, self)
834
def create_bundle(self, target, base, fileobj, format=None):
836
self._real_repository.create_bundle(target, base, fileobj, format)
839
def get_ancestry(self, revision_id, topo_sorted=True):
841
return self._real_repository.get_ancestry(revision_id, topo_sorted)
843
def fileids_altered_by_revision_ids(self, revision_ids):
845
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
847
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
849
return self._real_repository._get_versioned_file_checker(
850
revisions, revision_versions_cache)
852
def iter_files_bytes(self, desired_files):
853
"""See Repository.iter_file_bytes.
856
return self._real_repository.iter_files_bytes(desired_files)
859
def _fetch_order(self):
860
"""Decorate the real repository for now.
862
In the long term getting this back from the remote repository as part
863
of open would be more efficient.
866
# self._ensure_real()
867
# return self._real_repository._fetch_order
870
def _fetch_uses_deltas(self):
871
"""Decorate the real repository for now.
873
In the long term getting this back from the remote repository as part
874
of open would be more efficient.
877
return self._real_repository._fetch_uses_deltas
880
def _fetch_reconcile(self):
881
"""Decorate the real repository for now.
883
In the long term getting this back from the remote repository as part
884
of open would be more efficient.
887
return self._real_repository._fetch_reconcile
889
def get_parent_map(self, revision_ids):
890
"""See bzrlib.Graph.get_parent_map()."""
891
return self._make_parents_provider().get_parent_map(revision_ids)
893
def _get_parent_map_rpc(self, keys):
894
"""Helper for get_parent_map that performs the RPC."""
895
medium = self._client._medium
896
if medium._is_remote_before((1, 2)):
897
# We already found out that the server can't understand
898
# Repository.get_parent_map requests, so just fetch the whole
900
# XXX: Note that this will issue a deprecation warning. This is ok
901
# :- its because we're working with a deprecated server anyway, and
902
# the user will almost certainly have seen a warning about the
903
# server version already.
904
rg = self.get_revision_graph()
905
# There is an api discrepency between get_parent_map and
906
# get_revision_graph. Specifically, a "key:()" pair in
907
# get_revision_graph just means a node has no parents. For
908
# "get_parent_map" it means the node is a ghost. So fix up the
909
# graph to correct this.
910
# https://bugs.launchpad.net/bzr/+bug/214894
911
# There is one other "bug" which is that ghosts in
912
# get_revision_graph() are not returned at all. But we won't worry
913
# about that for now.
914
for node_id, parent_ids in rg.iteritems():
916
rg[node_id] = (NULL_REVISION,)
917
rg[NULL_REVISION] = ()
922
raise ValueError('get_parent_map(None) is not valid')
923
if NULL_REVISION in keys:
924
keys.discard(NULL_REVISION)
925
found_parents = {NULL_REVISION:()}
930
# TODO(Needs analysis): We could assume that the keys being requested
931
# from get_parent_map are in a breadth first search, so typically they
932
# will all be depth N from some common parent, and we don't have to
933
# have the server iterate from the root parent, but rather from the
934
# keys we're searching; and just tell the server the keyspace we
935
# already have; but this may be more traffic again.
937
# Transform self._parents_map into a search request recipe.
938
# TODO: Manage this incrementally to avoid covering the same path
939
# repeatedly. (The server will have to on each request, but the less
940
# work done the better).
941
parents_map = self._unstacked_provider.get_cached_map()
942
if parents_map is None:
943
# Repository is not locked, so there's no cache.
945
start_set = set(parents_map)
946
result_parents = set()
947
for parents in parents_map.itervalues():
948
result_parents.update(parents)
949
stop_keys = result_parents.difference(start_set)
950
included_keys = start_set.intersection(result_parents)
951
start_set.difference_update(included_keys)
952
recipe = (start_set, stop_keys, len(parents_map))
953
path = self.bzrdir._path_for_remote_call(self._client)
955
if type(key) is not str:
957
"key %r not a plain string" % (key,))
958
verb = 'Repository.get_parent_map'
959
args = (path,) + tuple(keys)
961
response = self._call_with_body_bytes_expecting_body(
962
verb, args, _serialise_search_recipe(recipe))
963
except errors.UnknownSmartMethod:
964
# Server does not support this method, so get the whole graph.
965
# Worse, we have to force a disconnection, because the server now
966
# doesn't realise it has a body on the wire to consume, so the
967
# only way to recover is to abandon the connection.
969
'Server is too old for fast get_parent_map, reconnecting. '
970
'(Upgrade the server to Bazaar 1.2 to avoid this)')
972
# To avoid having to disconnect repeatedly, we keep track of the
973
# fact the server doesn't understand remote methods added in 1.2.
974
medium._remember_remote_is_before((1, 2))
975
return self.get_revision_graph(None)
976
response_tuple, response_handler = response
977
if response_tuple[0] not in ['ok']:
978
response_handler.cancel_read_body()
979
raise errors.UnexpectedSmartServerResponse(response_tuple)
980
if response_tuple[0] == 'ok':
981
coded = bz2.decompress(response_handler.read_body_bytes())
985
lines = coded.split('\n')
988
d = tuple(line.split())
990
revision_graph[d[0]] = d[1:]
992
# No parents - so give the Graph result (NULL_REVISION,).
993
revision_graph[d[0]] = (NULL_REVISION,)
994
return revision_graph
997
def get_signature_text(self, revision_id):
999
return self._real_repository.get_signature_text(revision_id)
1002
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1003
def get_revision_graph_with_ghosts(self, revision_ids=None):
1005
return self._real_repository.get_revision_graph_with_ghosts(
1006
revision_ids=revision_ids)
1009
def get_inventory_xml(self, revision_id):
1011
return self._real_repository.get_inventory_xml(revision_id)
1013
def deserialise_inventory(self, revision_id, xml):
1015
return self._real_repository.deserialise_inventory(revision_id, xml)
1017
def reconcile(self, other=None, thorough=False):
1019
return self._real_repository.reconcile(other=other, thorough=thorough)
1021
def all_revision_ids(self):
1023
return self._real_repository.all_revision_ids()
1026
def get_deltas_for_revisions(self, revisions):
1028
return self._real_repository.get_deltas_for_revisions(revisions)
1031
def get_revision_delta(self, revision_id):
1033
return self._real_repository.get_revision_delta(revision_id)
1036
def revision_trees(self, revision_ids):
1038
return self._real_repository.revision_trees(revision_ids)
1041
def get_revision_reconcile(self, revision_id):
1043
return self._real_repository.get_revision_reconcile(revision_id)
1046
def check(self, revision_ids=None):
1048
return self._real_repository.check(revision_ids=revision_ids)
1050
def copy_content_into(self, destination, revision_id=None):
1052
return self._real_repository.copy_content_into(
1053
destination, revision_id=revision_id)
1055
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1056
# get a tarball of the remote repository, and copy from that into the
1058
from bzrlib import osutils
1060
# TODO: Maybe a progress bar while streaming the tarball?
1061
note("Copying repository content as tarball...")
1062
tar_file = self._get_tarball('bz2')
1063
if tar_file is None:
1065
destination = to_bzrdir.create_repository()
1067
tar = tarfile.open('repository', fileobj=tar_file,
1069
tmpdir = osutils.mkdtemp()
1071
_extract_tar(tar, tmpdir)
1072
tmp_bzrdir = BzrDir.open(tmpdir)
1073
tmp_repo = tmp_bzrdir.open_repository()
1074
tmp_repo.copy_content_into(destination, revision_id)
1076
osutils.rmtree(tmpdir)
1080
# TODO: Suggestion from john: using external tar is much faster than
1081
# python's tarfile library, but it may not work on windows.
1085
"""Compress the data within the repository.
1087
This is not currently implemented within the smart server.
1090
return self._real_repository.pack()
1092
def set_make_working_trees(self, new_value):
1094
self._real_repository.set_make_working_trees(new_value)
1097
def sign_revision(self, revision_id, gpg_strategy):
1099
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1102
def get_revisions(self, revision_ids):
1104
return self._real_repository.get_revisions(revision_ids)
1106
def supports_rich_root(self):
1108
return self._real_repository.supports_rich_root()
1110
def iter_reverse_revision_history(self, revision_id):
1112
return self._real_repository.iter_reverse_revision_history(revision_id)
1115
def _serializer(self):
1117
return self._real_repository._serializer
1119
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1121
return self._real_repository.store_revision_signature(
1122
gpg_strategy, plaintext, revision_id)
1124
def add_signature_text(self, revision_id, signature):
1126
return self._real_repository.add_signature_text(revision_id, signature)
1128
def has_signature_for_revision_id(self, revision_id):
1130
return self._real_repository.has_signature_for_revision_id(revision_id)
1132
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1134
return self._real_repository.item_keys_introduced_by(revision_ids,
1135
_files_pb=_files_pb)
1137
def revision_graph_can_have_wrong_parents(self):
1138
# The answer depends on the remote repo format.
1140
return self._real_repository.revision_graph_can_have_wrong_parents()
1142
def _find_inconsistent_revision_parents(self):
1144
return self._real_repository._find_inconsistent_revision_parents()
1146
def _check_for_inconsistent_revision_parents(self):
1148
return self._real_repository._check_for_inconsistent_revision_parents()
1150
def _make_parents_provider(self, other=None):
1151
providers = [self._unstacked_provider]
1152
if other is not None:
1153
providers.insert(0, other)
1154
providers.extend(r._make_parents_provider() for r in
1155
self._fallback_repositories)
1156
return graph._StackedParentsProvider(providers)
1159
path = self.bzrdir._path_for_remote_call(self._client)
1161
response = self._call('PackRepository.autopack', path)
1162
except errors.UnknownSmartMethod:
1164
self._real_repository._pack_collection.autopack()
1166
if self._real_repository is not None:
1167
# Reset the real repository's cache of pack names.
1168
# XXX: At some point we may be able to skip this and just rely on
1169
# the automatic retry logic to do the right thing, but for now we
1170
# err on the side of being correct rather than being optimal.
1171
self._real_repository._pack_collection.reload_pack_names()
1172
if response[0] != 'ok':
1173
raise errors.UnexpectedSmartServerResponse(response)
1176
def _serialise_search_recipe(recipe):
1177
"""Serialise a graph search recipe.
1179
:param recipe: A search recipe (start, stop, count).
1180
:return: Serialised bytes.
1182
start_keys = ' '.join(recipe[0])
1183
stop_keys = ' '.join(recipe[1])
1184
count = str(recipe[2])
1185
return '\n'.join((start_keys, stop_keys, count))
1188
def _serialise_record_stream(stream):
1189
"""Takes a record stream as returned by get_record_stream and yields bytes.
1191
# Yields bencode of (sha1, storage_kind, key, parents, build_details,
1194
# - if sha1 is None, sha1 is ''
1195
# - if parents is None, parents is 'nil' (to distinguish it from empty
1197
# - if record has no _build_details, build_details is ()
1198
for record in stream:
1202
parents = record.parents
1203
if record.parents is None:
1205
if record.storage_kind.startswith('knit-'):
1206
build_details = record._build_details
1209
struct = (sha1, record.storage_kind, record.key, parents,
1210
build_details, record.get_bytes_as(record.storage_kind))
1211
yield bencode.bencode(struct)
1214
class RemoteVersionedFiles(VersionedFiles):
1216
def __init__(self, remote_repo, vf_name):
1217
self.remote_repo = remote_repo
1218
self.vf_name = vf_name
1220
def _get_real_vf(self):
1221
self.remote_repo._ensure_real()
1222
return getattr(self.remote_repo._real_repository, self.vf_name)
1224
def add_lines(self, version_id, parents, lines, parent_texts=None,
1225
left_matching_blocks=None, nostore_sha=None, random_id=False,
1226
check_content=True):
1227
real_vf = self._get_real_vf()
1228
return real_vf.add_lines(version_id, parents, lines,
1229
parent_texts=parent_texts,
1230
left_matching_blocks=left_matching_blocks,
1231
nostore_sha=nostore_sha, random_id=random_id,
1232
check_content=check_content)
1234
def add_mpdiffs(self, records):
1235
real_vf = self._get_real_vf()
1236
return real_vf.add_mpdiffs(records)
1238
def annotate(self, key):
1239
real_vf = self._get_real_vf()
1240
return real_vf.annotate(key)
1242
def check(self, progress_bar=None):
1243
real_vf = self._get_real_vf()
1244
return real_vf.check(progress_bar=progress_bar)
1246
def get_parent_map(self, keys):
1247
real_vf = self._get_real_vf()
1248
return real_vf.get_parent_map(keys)
1250
def get_record_stream(self, keys, ordering, include_delta_closure):
1251
real_vf = self._get_real_vf()
1252
return real_vf.get_record_stream(keys, ordering, include_delta_closure)
1254
def get_sha1s(self, keys):
1255
real_vf = self._get_real_vf()
1256
return real_vf.get_sha1s(keys)
1258
def insert_record_stream(self, stream, _record_serialiser=None):
1259
lock_token = self.remote_repo._lock_token
1260
if lock_token is None:
1262
if _record_serialiser is None:
1263
_record_serialiser = _serialise_record_stream
1264
# Tee the stream, because we may need to replay it if we have to
1265
# fallback to the VFS implementation. This unfortunately means
1266
# the entire record stream will temporarily be buffered in memory, even
1267
# if we don't need to fallback.
1268
# TODO: remember if this server accepts the insert_record_stream RPC,
1269
# and if so skip the buffering. (And if not, fallback immediately,
1270
# again no buffering.)
1271
stream, fallback_stream = itertools.tee(stream)
1272
byte_stream = _record_serialiser(stream)
1273
client = self.remote_repo._client
1274
path = self.remote_repo.bzrdir._path_for_remote_call(client)
1276
response = client.call_with_body_stream(
1277
('VersionedFile.insert_record_stream', path, self.vf_name,
1278
lock_token), byte_stream)
1279
except errors.UnknownSmartMethod:
1280
real_vf = self._get_real_vf()
1281
return real_vf.insert_record_stream(fallback_stream)
1283
response_tuple, response_handler = response
1284
if response_tuple != ('ok',):
1285
raise errors.UnexpectedSmartServerResponse(response_tuple)
1287
def iter_lines_added_or_present_in_keys(self, keys, pb=None):
1288
real_vf = self._get_real_vf()
1289
return real_vf.iter_lines_added_or_present_in_keys(keys, pb=pb)
1292
real_vf = self._get_real_vf()
1293
return real_vf.keys()
1295
def make_mpdiffs(self, keys):
1296
real_vf = self._get_real_vf()
1297
return real_vf.make_mpdiffs(keys)
1300
class RemoteBranchLockableFiles(LockableFiles):
1301
"""A 'LockableFiles' implementation that talks to a smart server.
1303
This is not a public interface class.
1306
def __init__(self, bzrdir, _client):
1307
self.bzrdir = bzrdir
1308
self._client = _client
1309
self._need_find_modes = True
1310
LockableFiles.__init__(
1311
self, bzrdir.get_branch_transport(None),
1312
'lock', lockdir.LockDir)
1314
def _find_modes(self):
1315
# RemoteBranches don't let the client set the mode of control files.
1316
self._dir_mode = None
1317
self._file_mode = None
1320
class RemoteBranchFormat(branch.BranchFormat):
1323
super(RemoteBranchFormat, self).__init__()
1324
self._matchingbzrdir = RemoteBzrDirFormat()
1325
self._matchingbzrdir.set_branch_format(self)
1327
def __eq__(self, other):
1328
return (isinstance(other, RemoteBranchFormat) and
1329
self.__dict__ == other.__dict__)
1331
def get_format_description(self):
1332
return 'Remote BZR Branch'
1334
def get_format_string(self):
1335
return 'Remote BZR Branch'
1337
def open(self, a_bzrdir):
1338
return a_bzrdir.open_branch()
1340
def initialize(self, a_bzrdir):
1341
return a_bzrdir.create_branch()
1343
def supports_tags(self):
1344
# Remote branches might support tags, but we won't know until we
1345
# access the real remote branch.
1349
class RemoteBranch(branch.Branch, _RpcHelper):
1350
"""Branch stored on a server accessed by HPSS RPC.
1352
At the moment most operations are mapped down to simple file operations.
1355
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1357
"""Create a RemoteBranch instance.
1359
:param real_branch: An optional local implementation of the branch
1360
format, usually accessing the data via the VFS.
1361
:param _client: Private parameter for testing.
1363
# We intentionally don't call the parent class's __init__, because it
1364
# will try to assign to self.tags, which is a property in this subclass.
1365
# And the parent's __init__ doesn't do much anyway.
1366
self._revision_id_to_revno_cache = None
1367
self._revision_history_cache = None
1368
self._last_revision_info_cache = None
1369
self.bzrdir = remote_bzrdir
1370
if _client is not None:
1371
self._client = _client
1373
self._client = remote_bzrdir._client
1374
self.repository = remote_repository
1375
if real_branch is not None:
1376
self._real_branch = real_branch
1377
# Give the remote repository the matching real repo.
1378
real_repo = self._real_branch.repository
1379
if isinstance(real_repo, RemoteRepository):
1380
real_repo._ensure_real()
1381
real_repo = real_repo._real_repository
1382
self.repository._set_real_repository(real_repo)
1383
# Give the branch the remote repository to let fast-pathing happen.
1384
self._real_branch.repository = self.repository
1386
self._real_branch = None
1387
# Fill out expected attributes of branch for bzrlib api users.
1388
self._format = RemoteBranchFormat()
1389
self.base = self.bzrdir.root_transport.base
1390
self._control_files = None
1391
self._lock_mode = None
1392
self._lock_token = None
1393
self._repo_lock_token = None
1394
self._lock_count = 0
1395
self._leave_lock = False
1396
# The base class init is not called, so we duplicate this:
1397
hooks = branch.Branch.hooks['open']
1400
self._setup_stacking()
1402
def _setup_stacking(self):
1403
# configure stacking into the remote repository, by reading it from
1406
fallback_url = self.get_stacked_on_url()
1407
except (errors.NotStacked, errors.UnstackableBranchFormat,
1408
errors.UnstackableRepositoryFormat), e:
1410
# it's relative to this branch...
1411
fallback_url = urlutils.join(self.base, fallback_url)
1412
transports = [self.bzrdir.root_transport]
1413
if self._real_branch is not None:
1414
transports.append(self._real_branch._transport)
1415
stacked_on = branch.Branch.open(fallback_url,
1416
possible_transports=transports)
1417
self.repository.add_fallback_repository(stacked_on.repository)
1419
def _get_real_transport(self):
1420
# if we try vfs access, return the real branch's vfs transport
1422
return self._real_branch._transport
1424
_transport = property(_get_real_transport)
1427
return "%s(%s)" % (self.__class__.__name__, self.base)
1431
def _ensure_real(self):
1432
"""Ensure that there is a _real_branch set.
1434
Used before calls to self._real_branch.
1436
if self._real_branch is None:
1437
if not vfs.vfs_enabled():
1438
raise AssertionError('smart server vfs must be enabled '
1439
'to use vfs implementation')
1440
self.bzrdir._ensure_real()
1441
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1442
if self.repository._real_repository is None:
1443
# Give the remote repository the matching real repo.
1444
real_repo = self._real_branch.repository
1445
if isinstance(real_repo, RemoteRepository):
1446
real_repo._ensure_real()
1447
real_repo = real_repo._real_repository
1448
self.repository._set_real_repository(real_repo)
1449
# Give the real branch the remote repository to let fast-pathing
1451
self._real_branch.repository = self.repository
1452
if self._lock_mode == 'r':
1453
self._real_branch.lock_read()
1454
elif self._lock_mode == 'w':
1455
self._real_branch.lock_write(token=self._lock_token)
1457
def _translate_error(self, err, **context):
1458
self.repository._translate_error(err, branch=self, **context)
1460
def _clear_cached_state(self):
1461
super(RemoteBranch, self)._clear_cached_state()
1462
if self._real_branch is not None:
1463
self._real_branch._clear_cached_state()
1465
def _clear_cached_state_of_remote_branch_only(self):
1466
"""Like _clear_cached_state, but doesn't clear the cache of
1469
This is useful when falling back to calling a method of
1470
self._real_branch that changes state. In that case the underlying
1471
branch changes, so we need to invalidate this RemoteBranch's cache of
1472
it. However, there's no need to invalidate the _real_branch's cache
1473
too, in fact doing so might harm performance.
1475
super(RemoteBranch, self)._clear_cached_state()
1478
def control_files(self):
1479
# Defer actually creating RemoteBranchLockableFiles until its needed,
1480
# because it triggers an _ensure_real that we otherwise might not need.
1481
if self._control_files is None:
1482
self._control_files = RemoteBranchLockableFiles(
1483
self.bzrdir, self._client)
1484
return self._control_files
1486
def _get_checkout_format(self):
1488
return self._real_branch._get_checkout_format()
1490
def get_physical_lock_status(self):
1491
"""See Branch.get_physical_lock_status()."""
1492
# should be an API call to the server, as branches must be lockable.
1494
return self._real_branch.get_physical_lock_status()
1496
def get_stacked_on_url(self):
1497
"""Get the URL this branch is stacked against.
1499
:raises NotStacked: If the branch is not stacked.
1500
:raises UnstackableBranchFormat: If the branch does not support
1502
:raises UnstackableRepositoryFormat: If the repository does not support
1506
# there may not be a repository yet, so we can't use
1507
# self._translate_error, so we can't use self._call either.
1508
response = self._client.call('Branch.get_stacked_on_url',
1509
self._remote_path())
1510
except errors.ErrorFromSmartServer, err:
1511
# there may not be a repository yet, so we can't call through
1512
# its _translate_error
1513
_translate_error(err, branch=self)
1514
except errors.UnknownSmartMethod, err:
1516
return self._real_branch.get_stacked_on_url()
1517
if response[0] != 'ok':
1518
raise errors.UnexpectedSmartServerResponse(response)
1521
def lock_read(self):
1522
self.repository.lock_read()
1523
if not self._lock_mode:
1524
self._lock_mode = 'r'
1525
self._lock_count = 1
1526
if self._real_branch is not None:
1527
self._real_branch.lock_read()
1529
self._lock_count += 1
1531
def _remote_lock_write(self, token):
1533
branch_token = repo_token = ''
1535
branch_token = token
1536
repo_token = self.repository.lock_write()
1537
self.repository.unlock()
1538
err_context = {'token': token}
1539
response = self._call(
1540
'Branch.lock_write', self._remote_path(), branch_token,
1541
repo_token or '', **err_context)
1542
if response[0] != 'ok':
1543
raise errors.UnexpectedSmartServerResponse(response)
1544
ok, branch_token, repo_token = response
1545
return branch_token, repo_token
1547
def lock_write(self, token=None):
1548
if not self._lock_mode:
1549
# Lock the branch and repo in one remote call.
1550
remote_tokens = self._remote_lock_write(token)
1551
self._lock_token, self._repo_lock_token = remote_tokens
1552
if not self._lock_token:
1553
raise SmartProtocolError('Remote server did not return a token!')
1554
# Tell the self.repository object that it is locked.
1555
self.repository.lock_write(
1556
self._repo_lock_token, _skip_rpc=True)
1558
if self._real_branch is not None:
1559
self._real_branch.lock_write(token=self._lock_token)
1560
if token is not None:
1561
self._leave_lock = True
1563
self._leave_lock = False
1564
self._lock_mode = 'w'
1565
self._lock_count = 1
1566
elif self._lock_mode == 'r':
1567
raise errors.ReadOnlyTransaction
1569
if token is not None:
1570
# A token was given to lock_write, and we're relocking, so
1571
# check that the given token actually matches the one we
1573
if token != self._lock_token:
1574
raise errors.TokenMismatch(token, self._lock_token)
1575
self._lock_count += 1
1576
# Re-lock the repository too.
1577
self.repository.lock_write(self._repo_lock_token)
1578
return self._lock_token or None
1580
def _unlock(self, branch_token, repo_token):
1581
err_context = {'token': str((branch_token, repo_token))}
1582
response = self._call(
1583
'Branch.unlock', self._remote_path(), branch_token,
1584
repo_token or '', **err_context)
1585
if response == ('ok',):
1587
raise errors.UnexpectedSmartServerResponse(response)
1591
self._lock_count -= 1
1592
if not self._lock_count:
1593
self._clear_cached_state()
1594
mode = self._lock_mode
1595
self._lock_mode = None
1596
if self._real_branch is not None:
1597
if (not self._leave_lock and mode == 'w' and
1598
self._repo_lock_token):
1599
# If this RemoteBranch will remove the physical lock
1600
# for the repository, make sure the _real_branch
1601
# doesn't do it first. (Because the _real_branch's
1602
# repository is set to be the RemoteRepository.)
1603
self._real_branch.repository.leave_lock_in_place()
1604
self._real_branch.unlock()
1606
# Only write-locked branched need to make a remote method
1607
# call to perfom the unlock.
1609
if not self._lock_token:
1610
raise AssertionError('Locked, but no token!')
1611
branch_token = self._lock_token
1612
repo_token = self._repo_lock_token
1613
self._lock_token = None
1614
self._repo_lock_token = None
1615
if not self._leave_lock:
1616
self._unlock(branch_token, repo_token)
1618
self.repository.unlock()
1620
def break_lock(self):
1622
return self._real_branch.break_lock()
1624
def leave_lock_in_place(self):
1625
if not self._lock_token:
1626
raise NotImplementedError(self.leave_lock_in_place)
1627
self._leave_lock = True
1629
def dont_leave_lock_in_place(self):
1630
if not self._lock_token:
1631
raise NotImplementedError(self.dont_leave_lock_in_place)
1632
self._leave_lock = False
1634
def _last_revision_info(self):
1635
response = self._call('Branch.last_revision_info', self._remote_path())
1636
if response[0] != 'ok':
1637
raise SmartProtocolError('unexpected response code %s' % (response,))
1638
revno = int(response[1])
1639
last_revision = response[2]
1640
return (revno, last_revision)
1642
def _gen_revision_history(self):
1643
"""See Branch._gen_revision_history()."""
1644
response_tuple, response_handler = self._call_expecting_body(
1645
'Branch.revision_history', self._remote_path())
1646
if response_tuple[0] != 'ok':
1647
raise errors.UnexpectedSmartServerResponse(response_tuple)
1648
result = response_handler.read_body_bytes().split('\x00')
1653
def _remote_path(self):
1654
return self.bzrdir._path_for_remote_call(self._client)
1656
def _set_last_revision_descendant(self, revision_id, other_branch,
1657
allow_diverged=False, allow_overwrite_descendant=False):
1658
err_context = {'other_branch': other_branch}
1659
response = self._call('Branch.set_last_revision_ex',
1660
self._remote_path(), self._lock_token, self._repo_lock_token,
1661
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1663
self._clear_cached_state()
1664
if len(response) != 3 and response[0] != 'ok':
1665
raise errors.UnexpectedSmartServerResponse(response)
1666
new_revno, new_revision_id = response[1:]
1667
self._last_revision_info_cache = new_revno, new_revision_id
1668
if self._real_branch is not None:
1669
cache = new_revno, new_revision_id
1670
self._real_branch._last_revision_info_cache = cache
1672
def _set_last_revision(self, revision_id):
1673
self._clear_cached_state()
1674
response = self._call('Branch.set_last_revision',
1675
self._remote_path(), self._lock_token, self._repo_lock_token,
1677
if response != ('ok',):
1678
raise errors.UnexpectedSmartServerResponse(response)
1681
def set_revision_history(self, rev_history):
1682
# Send just the tip revision of the history; the server will generate
1683
# the full history from that. If the revision doesn't exist in this
1684
# branch, NoSuchRevision will be raised.
1685
if rev_history == []:
1688
rev_id = rev_history[-1]
1689
self._set_last_revision(rev_id)
1690
self._cache_revision_history(rev_history)
1692
def get_parent(self):
1694
return self._real_branch.get_parent()
1696
def set_parent(self, url):
1698
return self._real_branch.set_parent(url)
1700
def set_stacked_on_url(self, stacked_location):
1701
"""Set the URL this branch is stacked against.
1703
:raises UnstackableBranchFormat: If the branch does not support
1705
:raises UnstackableRepositoryFormat: If the repository does not support
1709
return self._real_branch.set_stacked_on_url(stacked_location)
1711
def sprout(self, to_bzrdir, revision_id=None):
1712
branch_format = to_bzrdir._format._branch_format
1713
if (branch_format is None or
1714
isinstance(branch_format, RemoteBranchFormat)):
1715
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1716
# implies the same thing), but RemoteBranches can't be created at
1717
# arbitrary URLs. So create a branch in the same format as
1718
# _real_branch instead.
1719
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1720
# to_bzrdir.create_branch to create a RemoteBranch after all...
1722
result = self._real_branch._format.initialize(to_bzrdir)
1723
self.copy_content_into(result, revision_id=revision_id)
1724
result.set_parent(self.bzrdir.root_transport.base)
1726
result = branch.Branch.sprout(
1727
self, to_bzrdir, revision_id=revision_id)
1731
def pull(self, source, overwrite=False, stop_revision=None,
1733
self._clear_cached_state_of_remote_branch_only()
1735
return self._real_branch.pull(
1736
source, overwrite=overwrite, stop_revision=stop_revision,
1737
_override_hook_target=self, **kwargs)
1740
def push(self, target, overwrite=False, stop_revision=None):
1742
return self._real_branch.push(
1743
target, overwrite=overwrite, stop_revision=stop_revision,
1744
_override_hook_source_branch=self)
1746
def is_locked(self):
1747
return self._lock_count >= 1
1750
def revision_id_to_revno(self, revision_id):
1752
return self._real_branch.revision_id_to_revno(revision_id)
1755
def set_last_revision_info(self, revno, revision_id):
1756
revision_id = ensure_null(revision_id)
1758
response = self._call('Branch.set_last_revision_info',
1759
self._remote_path(), self._lock_token, self._repo_lock_token,
1760
str(revno), revision_id)
1761
except errors.UnknownSmartMethod:
1763
self._clear_cached_state_of_remote_branch_only()
1764
self._real_branch.set_last_revision_info(revno, revision_id)
1765
self._last_revision_info_cache = revno, revision_id
1767
if response == ('ok',):
1768
self._clear_cached_state()
1769
self._last_revision_info_cache = revno, revision_id
1770
# Update the _real_branch's cache too.
1771
if self._real_branch is not None:
1772
cache = self._last_revision_info_cache
1773
self._real_branch._last_revision_info_cache = cache
1775
raise errors.UnexpectedSmartServerResponse(response)
1778
def generate_revision_history(self, revision_id, last_rev=None,
1780
medium = self._client._medium
1781
if not medium._is_remote_before((1, 6)):
1783
self._set_last_revision_descendant(revision_id, other_branch,
1784
allow_diverged=True, allow_overwrite_descendant=True)
1786
except errors.UnknownSmartMethod:
1787
medium._remember_remote_is_before((1, 6))
1788
self._clear_cached_state_of_remote_branch_only()
1790
self._real_branch.generate_revision_history(
1791
revision_id, last_rev=last_rev, other_branch=other_branch)
1796
return self._real_branch.tags
1798
def set_push_location(self, location):
1800
return self._real_branch.set_push_location(location)
1803
def update_revisions(self, other, stop_revision=None, overwrite=False,
1805
"""See Branch.update_revisions."""
1808
if stop_revision is None:
1809
stop_revision = other.last_revision()
1810
if revision.is_null(stop_revision):
1811
# if there are no commits, we're done.
1813
self.fetch(other, stop_revision)
1816
# Just unconditionally set the new revision. We don't care if
1817
# the branches have diverged.
1818
self._set_last_revision(stop_revision)
1820
medium = self._client._medium
1821
if not medium._is_remote_before((1, 6)):
1823
self._set_last_revision_descendant(stop_revision, other)
1825
except errors.UnknownSmartMethod:
1826
medium._remember_remote_is_before((1, 6))
1827
# Fallback for pre-1.6 servers: check for divergence
1828
# client-side, then do _set_last_revision.
1829
last_rev = revision.ensure_null(self.last_revision())
1831
graph = self.repository.get_graph()
1832
if self._check_if_descendant_or_diverged(
1833
stop_revision, last_rev, graph, other):
1834
# stop_revision is a descendant of last_rev, but we aren't
1835
# overwriting, so we're done.
1837
self._set_last_revision(stop_revision)
1842
def _extract_tar(tar, to_dir):
1843
"""Extract all the contents of a tarfile object.
1845
A replacement for extractall, which is not present in python2.4
1848
tar.extract(tarinfo, to_dir)
1851
def _translate_error(err, **context):
1852
"""Translate an ErrorFromSmartServer into a more useful error.
1854
Possible context keys:
1862
If the error from the server doesn't match a known pattern, then
1863
UnknownErrorFromSmartServer is raised.
1867
return context[name]
1868
except KeyError, key_err:
1869
mutter('Missing key %r in context %r', key_err.args[0], context)
1872
"""Get the path from the context if present, otherwise use first error
1876
return context['path']
1877
except KeyError, key_err:
1879
return err.error_args[0]
1880
except IndexError, idx_err:
1882
'Missing key %r in context %r', key_err.args[0], context)
1885
if err.error_verb == 'NoSuchRevision':
1886
raise NoSuchRevision(find('branch'), err.error_args[0])
1887
elif err.error_verb == 'nosuchrevision':
1888
raise NoSuchRevision(find('repository'), err.error_args[0])
1889
elif err.error_tuple == ('nobranch',):
1890
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1891
elif err.error_verb == 'norepository':
1892
raise errors.NoRepositoryPresent(find('bzrdir'))
1893
elif err.error_verb == 'LockContention':
1894
raise errors.LockContention('(remote lock)')
1895
elif err.error_verb == 'UnlockableTransport':
1896
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1897
elif err.error_verb == 'LockFailed':
1898
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1899
elif err.error_verb == 'TokenMismatch':
1900
raise errors.TokenMismatch(find('token'), '(remote token)')
1901
elif err.error_verb == 'Diverged':
1902
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1903
elif err.error_verb == 'TipChangeRejected':
1904
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1905
elif err.error_verb == 'UnstackableBranchFormat':
1906
raise errors.UnstackableBranchFormat(*err.error_args)
1907
elif err.error_verb == 'UnstackableRepositoryFormat':
1908
raise errors.UnstackableRepositoryFormat(*err.error_args)
1909
elif err.error_verb == 'NotStacked':
1910
raise errors.NotStacked(branch=find('branch'))
1911
elif err.error_verb == 'PermissionDenied':
1913
if len(err.error_args) >= 2:
1914
extra = err.error_args[1]
1917
raise errors.PermissionDenied(path, extra=extra)
1918
elif err.error_verb == 'ReadError':
1920
raise errors.ReadError(path)
1921
elif err.error_verb == 'NoSuchFile':
1923
raise errors.NoSuchFile(path)
1924
elif err.error_verb == 'FileExists':
1925
raise errors.FileExists(err.error_args[0])
1926
elif err.error_verb == 'DirectoryNotEmpty':
1927
raise errors.DirectoryNotEmpty(err.error_args[0])
1928
elif err.error_verb == 'ShortReadvError':
1929
args = err.error_args
1930
raise errors.ShortReadvError(
1931
args[0], int(args[1]), int(args[2]), int(args[3]))
1932
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1933
encoding = str(err.error_args[0]) # encoding must always be a string
1934
val = err.error_args[1]
1935
start = int(err.error_args[2])
1936
end = int(err.error_args[3])
1937
reason = str(err.error_args[4]) # reason must always be a string
1938
if val.startswith('u:'):
1939
val = val[2:].decode('utf-8')
1940
elif val.startswith('s:'):
1941
val = val[2:].decode('base64')
1942
if err.error_verb == 'UnicodeDecodeError':
1943
raise UnicodeDecodeError(encoding, val, start, end, reason)
1944
elif err.error_verb == 'UnicodeEncodeError':
1945
raise UnicodeEncodeError(encoding, val, start, end, reason)
1946
elif err.error_verb == 'ReadOnlyError':
1947
raise errors.TransportNotPossible('readonly transport')
1948
raise errors.UnknownErrorFromSmartServer(err)