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, format, _client=None):
75
"""Construct a RemoteBzrDir.
77
:param _client: Private parameter for testing. Disables probing and the
80
BzrDir.__init__(self, transport, format)
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):
116
# as per meta1 formats - just delegate to the format object which may
118
result = self._format.repository_format.initialize(self, shared)
119
if not isinstance(result, RemoteRepository):
120
return self.open_repository()
124
def destroy_repository(self):
125
"""See BzrDir.destroy_repository"""
127
self._real_bzrdir.destroy_repository()
129
def create_branch(self):
130
# as per meta1 formats - just delegate to the format object which may
132
real_branch = self._format.get_branch_format().initialize(self)
133
if not isinstance(real_branch, RemoteBranch):
134
return RemoteBranch(self, self.find_repository(), real_branch)
138
def destroy_branch(self):
139
"""See BzrDir.destroy_branch"""
141
self._real_bzrdir.destroy_branch()
143
def create_workingtree(self, revision_id=None, from_branch=None):
144
raise errors.NotLocalUrl(self.transport.base)
146
def find_branch_format(self):
147
"""Find the branch 'format' for this bzrdir.
149
This might be a synthetic object for e.g. RemoteBranch and SVN.
151
b = self.open_branch()
154
def get_branch_reference(self):
155
"""See BzrDir.get_branch_reference()."""
156
path = self._path_for_remote_call(self._client)
157
response = self._call('BzrDir.open_branch', path)
158
if response[0] == 'ok':
159
if response[1] == '':
160
# branch at this location.
163
# a branch reference, use the existing BranchReference logic.
166
raise errors.UnexpectedSmartServerResponse(response)
168
def _get_tree_branch(self):
169
"""See BzrDir._get_tree_branch()."""
170
return None, self.open_branch()
172
def open_branch(self, _unsupported=False):
174
raise NotImplementedError('unsupported flag support not implemented yet.')
175
reference_url = self.get_branch_reference()
176
if reference_url is None:
177
# branch at this location.
178
return RemoteBranch(self, self.find_repository())
180
# a branch reference, use the existing BranchReference logic.
181
format = BranchReferenceFormat()
182
return format.open(self, _found=True, location=reference_url)
184
def open_repository(self):
185
path = self._path_for_remote_call(self._client)
186
verb = 'BzrDir.find_repositoryV2'
188
response = self._call(verb, path)
189
except errors.UnknownSmartMethod:
190
verb = 'BzrDir.find_repository'
191
response = self._call(verb, path)
192
if response[0] != 'ok':
193
raise errors.UnexpectedSmartServerResponse(response)
194
if verb == 'BzrDir.find_repository':
195
# servers that don't support the V2 method don't support external
197
response = response + ('no', )
198
if not (len(response) == 5):
199
raise SmartProtocolError('incorrect response length %s' % (response,))
200
if response[1] == '':
201
format = RemoteRepositoryFormat()
202
format.rich_root_data = (response[2] == 'yes')
203
format.supports_tree_reference = (response[3] == 'yes')
204
# No wire format to check this yet.
205
format.supports_external_lookups = (response[4] == 'yes')
206
# Used to support creating a real format instance when needed.
207
format._creating_bzrdir = self
208
remote_repo = RemoteRepository(self, format)
209
format._creating_repo = remote_repo
212
raise errors.NoRepositoryPresent(self)
214
def open_workingtree(self, recommend_upgrade=True):
216
if self._real_bzrdir.has_workingtree():
217
raise errors.NotLocalUrl(self.root_transport)
219
raise errors.NoWorkingTree(self.root_transport.base)
221
def _path_for_remote_call(self, client):
222
"""Return the path to be used for this bzrdir in a remote call."""
223
return client.remote_path_from_transport(self.root_transport)
225
def get_branch_transport(self, branch_format):
227
return self._real_bzrdir.get_branch_transport(branch_format)
229
def get_repository_transport(self, repository_format):
231
return self._real_bzrdir.get_repository_transport(repository_format)
233
def get_workingtree_transport(self, workingtree_format):
235
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
237
def can_convert_format(self):
238
"""Upgrading of remote bzrdirs is not supported yet."""
241
def needs_format_conversion(self, format=None):
242
"""Upgrading of remote bzrdirs is not supported yet."""
244
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
245
% 'needs_format_conversion(format=None)')
248
def clone(self, url, revision_id=None, force_new_repo=False,
249
preserve_stacking=False):
251
return self._real_bzrdir.clone(url, revision_id=revision_id,
252
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
254
def get_config(self):
256
return self._real_bzrdir.get_config()
259
class RemoteRepositoryFormat(repository.RepositoryFormat):
260
"""Format for repositories accessed over a _SmartClient.
262
Instances of this repository are represented by RemoteRepository
265
The RemoteRepositoryFormat is parameterized during construction
266
to reflect the capabilities of the real, remote format. Specifically
267
the attributes rich_root_data and supports_tree_reference are set
268
on a per instance basis, and are not set (and should not be) at
271
:ivar _custom_format: If set, a specific concrete repository format that
272
will be used when initializing a repository with this
273
RemoteRepositoryFormat.
274
:ivar _creating_repo: If set, the repository object that this
275
RemoteRepositoryFormat was created for: it can be called into
276
to obtain data like the network name.
279
_matchingbzrdir = RemoteBzrDirFormat()
282
repository.RepositoryFormat.__init__(self)
283
self._custom_format = None
285
def initialize(self, a_bzrdir, shared=False):
286
if self._custom_format:
287
# This returns a custom instance - e.g. a pack repo, not a remote
289
return self._custom_format.initialize(a_bzrdir, shared=shared)
290
if not isinstance(a_bzrdir, RemoteBzrDir):
291
prior_repo = self._creating_bzrdir.open_repository()
292
prior_repo._ensure_real()
293
return prior_repo._real_repository._format.initialize(
294
a_bzrdir, shared=shared)
295
# delegate to a real object at this point (remoteBzrDir delegate to the
296
# repository format which would lead to infinite recursion).
297
a_bzrdir._ensure_real()
298
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
299
if not isinstance(result, RemoteRepository):
300
return self.open(a_bzrdir)
304
def open(self, a_bzrdir):
305
if not isinstance(a_bzrdir, RemoteBzrDir):
306
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
307
return a_bzrdir.open_repository()
309
def get_format_description(self):
310
return 'bzr remote repository'
312
def __eq__(self, other):
313
return self.__class__ == other.__class__
315
def check_conversion_target(self, target_format):
316
if self.rich_root_data and not target_format.rich_root_data:
317
raise errors.BadConversionTarget(
318
'Does not support rich root data.', target_format)
319
if (self.supports_tree_reference and
320
not getattr(target_format, 'supports_tree_reference', False)):
321
raise errors.BadConversionTarget(
322
'Does not support nested trees', target_format)
324
def network_name(self):
325
self._creating_repo._ensure_real()
326
return self._creating_repo._real_repository._format.network_name()
329
class RemoteRepository(_RpcHelper):
330
"""Repository accessed over rpc.
332
For the moment most operations are performed using local transport-backed
336
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
337
"""Create a RemoteRepository instance.
339
:param remote_bzrdir: The bzrdir hosting this repository.
340
:param format: The RemoteFormat object to use.
341
:param real_repository: If not None, a local implementation of the
342
repository logic for the repository, usually accessing the data
344
:param _client: Private testing parameter - override the smart client
345
to be used by the repository.
348
self._real_repository = real_repository
350
self._real_repository = None
351
self.bzrdir = remote_bzrdir
353
self._client = remote_bzrdir._client
355
self._client = _client
356
self._format = format
357
self._lock_mode = None
358
self._lock_token = None
360
self._leave_lock = False
361
self._unstacked_provider = graph.CachingParentsProvider(
362
get_parent_map=self._get_parent_map_rpc)
363
self._unstacked_provider.disable_cache()
365
# These depend on the actual remote format, so force them off for
366
# maximum compatibility. XXX: In future these should depend on the
367
# remote repository instance, but this is irrelevant until we perform
368
# reconcile via an RPC call.
369
self._reconcile_does_inventory_gc = False
370
self._reconcile_fixes_text_parents = False
371
self._reconcile_backsup_inventory = False
372
self.base = self.bzrdir.transport.base
373
# Additional places to query for data.
374
self._fallback_repositories = []
377
return "%s(%s)" % (self.__class__.__name__, self.base)
381
def abort_write_group(self, suppress_errors=False):
382
"""Complete a write group on the decorated repository.
384
Smart methods peform operations in a single step so this api
385
is not really applicable except as a compatibility thunk
386
for older plugins that don't use e.g. the CommitBuilder
389
:param suppress_errors: see Repository.abort_write_group.
392
return self._real_repository.abort_write_group(
393
suppress_errors=suppress_errors)
395
def commit_write_group(self):
396
"""Complete a write group on the decorated repository.
398
Smart methods peform operations in a single step so this api
399
is not really applicable except as a compatibility thunk
400
for older plugins that don't use e.g. the CommitBuilder
404
return self._real_repository.commit_write_group()
406
def _ensure_real(self):
407
"""Ensure that there is a _real_repository set.
409
Used before calls to self._real_repository.
411
if self._real_repository is None:
412
self.bzrdir._ensure_real()
413
self._set_real_repository(
414
self.bzrdir._real_bzrdir.open_repository())
416
def _translate_error(self, err, **context):
417
self.bzrdir._translate_error(err, repository=self, **context)
419
def find_text_key_references(self):
420
"""Find the text key references within the repository.
422
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
423
revision_ids. Each altered file-ids has the exact revision_ids that
424
altered it listed explicitly.
425
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
426
to whether they were referred to by the inventory of the
427
revision_id that they contain. The inventory texts from all present
428
revision ids are assessed to generate this report.
431
return self._real_repository.find_text_key_references()
433
def _generate_text_key_index(self):
434
"""Generate a new text key index for the repository.
436
This is an expensive function that will take considerable time to run.
438
:return: A dict mapping (file_id, revision_id) tuples to a list of
439
parents, also (file_id, revision_id) tuples.
442
return self._real_repository._generate_text_key_index()
444
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
445
def get_revision_graph(self, revision_id=None):
446
"""See Repository.get_revision_graph()."""
447
return self._get_revision_graph(revision_id)
449
def _get_revision_graph(self, revision_id):
450
"""Private method for using with old (< 1.2) servers to fallback."""
451
if revision_id is None:
453
elif revision.is_null(revision_id):
456
path = self.bzrdir._path_for_remote_call(self._client)
457
response = self._call_expecting_body(
458
'Repository.get_revision_graph', path, revision_id)
459
response_tuple, response_handler = response
460
if response_tuple[0] != 'ok':
461
raise errors.UnexpectedSmartServerResponse(response_tuple)
462
coded = response_handler.read_body_bytes()
464
# no revisions in this repository!
466
lines = coded.split('\n')
469
d = tuple(line.split())
470
revision_graph[d[0]] = d[1:]
472
return revision_graph
474
def has_revision(self, revision_id):
475
"""See Repository.has_revision()."""
476
if revision_id == NULL_REVISION:
477
# The null revision is always present.
479
path = self.bzrdir._path_for_remote_call(self._client)
480
response = self._call('Repository.has_revision', path, revision_id)
481
if response[0] not in ('yes', 'no'):
482
raise errors.UnexpectedSmartServerResponse(response)
483
if response[0] == 'yes':
485
for fallback_repo in self._fallback_repositories:
486
if fallback_repo.has_revision(revision_id):
490
def has_revisions(self, revision_ids):
491
"""See Repository.has_revisions()."""
492
# FIXME: This does many roundtrips, particularly when there are
493
# fallback repositories. -- mbp 20080905
495
for revision_id in revision_ids:
496
if self.has_revision(revision_id):
497
result.add(revision_id)
500
def has_same_location(self, other):
501
return (self.__class__ == other.__class__ and
502
self.bzrdir.transport.base == other.bzrdir.transport.base)
504
def get_graph(self, other_repository=None):
505
"""Return the graph for this repository format"""
506
parents_provider = self._make_parents_provider(other_repository)
507
return graph.Graph(parents_provider)
509
def gather_stats(self, revid=None, committers=None):
510
"""See Repository.gather_stats()."""
511
path = self.bzrdir._path_for_remote_call(self._client)
512
# revid can be None to indicate no revisions, not just NULL_REVISION
513
if revid is None or revision.is_null(revid):
517
if committers is None or not committers:
518
fmt_committers = 'no'
520
fmt_committers = 'yes'
521
response_tuple, response_handler = self._call_expecting_body(
522
'Repository.gather_stats', path, fmt_revid, fmt_committers)
523
if response_tuple[0] != 'ok':
524
raise errors.UnexpectedSmartServerResponse(response_tuple)
526
body = response_handler.read_body_bytes()
528
for line in body.split('\n'):
531
key, val_text = line.split(':')
532
if key in ('revisions', 'size', 'committers'):
533
result[key] = int(val_text)
534
elif key in ('firstrev', 'latestrev'):
535
values = val_text.split(' ')[1:]
536
result[key] = (float(values[0]), long(values[1]))
540
def find_branches(self, using=False):
541
"""See Repository.find_branches()."""
542
# should be an API call to the server.
544
return self._real_repository.find_branches(using=using)
546
def get_physical_lock_status(self):
547
"""See Repository.get_physical_lock_status()."""
548
# should be an API call to the server.
550
return self._real_repository.get_physical_lock_status()
552
def is_in_write_group(self):
553
"""Return True if there is an open write group.
555
write groups are only applicable locally for the smart server..
557
if self._real_repository:
558
return self._real_repository.is_in_write_group()
561
return self._lock_count >= 1
564
"""See Repository.is_shared()."""
565
path = self.bzrdir._path_for_remote_call(self._client)
566
response = self._call('Repository.is_shared', path)
567
if response[0] not in ('yes', 'no'):
568
raise SmartProtocolError('unexpected response code %s' % (response,))
569
return response[0] == 'yes'
571
def is_write_locked(self):
572
return self._lock_mode == 'w'
575
# wrong eventually - want a local lock cache context
576
if not self._lock_mode:
577
self._lock_mode = 'r'
579
self._unstacked_provider.enable_cache(cache_misses=False)
580
if self._real_repository is not None:
581
self._real_repository.lock_read()
583
self._lock_count += 1
585
def _remote_lock_write(self, token):
586
path = self.bzrdir._path_for_remote_call(self._client)
589
err_context = {'token': token}
590
response = self._call('Repository.lock_write', path, token,
592
if response[0] == 'ok':
596
raise errors.UnexpectedSmartServerResponse(response)
598
def lock_write(self, token=None, _skip_rpc=False):
599
if not self._lock_mode:
601
if self._lock_token is not None:
602
if token != self._lock_token:
603
raise errors.TokenMismatch(token, self._lock_token)
604
self._lock_token = token
606
self._lock_token = self._remote_lock_write(token)
607
# if self._lock_token is None, then this is something like packs or
608
# svn where we don't get to lock the repo, or a weave style repository
609
# where we cannot lock it over the wire and attempts to do so will
611
if self._real_repository is not None:
612
self._real_repository.lock_write(token=self._lock_token)
613
if token is not None:
614
self._leave_lock = True
616
self._leave_lock = False
617
self._lock_mode = 'w'
619
self._unstacked_provider.enable_cache(cache_misses=False)
620
elif self._lock_mode == 'r':
621
raise errors.ReadOnlyError(self)
623
self._lock_count += 1
624
return self._lock_token or None
626
def leave_lock_in_place(self):
627
if not self._lock_token:
628
raise NotImplementedError(self.leave_lock_in_place)
629
self._leave_lock = True
631
def dont_leave_lock_in_place(self):
632
if not self._lock_token:
633
raise NotImplementedError(self.dont_leave_lock_in_place)
634
self._leave_lock = False
636
def _set_real_repository(self, repository):
637
"""Set the _real_repository for this repository.
639
:param repository: The repository to fallback to for non-hpss
640
implemented operations.
642
if self._real_repository is not None:
643
raise AssertionError('_real_repository is already set')
644
if isinstance(repository, RemoteRepository):
645
raise AssertionError()
646
self._real_repository = repository
647
for fb in self._fallback_repositories:
648
self._real_repository.add_fallback_repository(fb)
649
if self._lock_mode == 'w':
650
# if we are already locked, the real repository must be able to
651
# acquire the lock with our token.
652
self._real_repository.lock_write(self._lock_token)
653
elif self._lock_mode == 'r':
654
self._real_repository.lock_read()
656
def start_write_group(self):
657
"""Start a write group on the decorated repository.
659
Smart methods peform operations in a single step so this api
660
is not really applicable except as a compatibility thunk
661
for older plugins that don't use e.g. the CommitBuilder
665
return self._real_repository.start_write_group()
667
def _unlock(self, token):
668
path = self.bzrdir._path_for_remote_call(self._client)
670
# with no token the remote repository is not persistently locked.
672
err_context = {'token': token}
673
response = self._call('Repository.unlock', path, token,
675
if response == ('ok',):
678
raise errors.UnexpectedSmartServerResponse(response)
681
self._lock_count -= 1
682
if self._lock_count > 0:
684
self._unstacked_provider.disable_cache()
685
old_mode = self._lock_mode
686
self._lock_mode = None
688
# The real repository is responsible at present for raising an
689
# exception if it's in an unfinished write group. However, it
690
# normally will *not* actually remove the lock from disk - that's
691
# done by the server on receiving the Repository.unlock call.
692
# This is just to let the _real_repository stay up to date.
693
if self._real_repository is not None:
694
self._real_repository.unlock()
696
# The rpc-level lock should be released even if there was a
697
# problem releasing the vfs-based lock.
699
# Only write-locked repositories need to make a remote method
700
# call to perfom the unlock.
701
old_token = self._lock_token
702
self._lock_token = None
703
if not self._leave_lock:
704
self._unlock(old_token)
706
def break_lock(self):
707
# should hand off to the network
709
return self._real_repository.break_lock()
711
def _get_tarball(self, compression):
712
"""Return a TemporaryFile containing a repository tarball.
714
Returns None if the server does not support sending tarballs.
717
path = self.bzrdir._path_for_remote_call(self._client)
719
response, protocol = self._call_expecting_body(
720
'Repository.tarball', path, compression)
721
except errors.UnknownSmartMethod:
722
protocol.cancel_read_body()
724
if response[0] == 'ok':
725
# Extract the tarball and return it
726
t = tempfile.NamedTemporaryFile()
727
# TODO: rpc layer should read directly into it...
728
t.write(protocol.read_body_bytes())
731
raise errors.UnexpectedSmartServerResponse(response)
733
def sprout(self, to_bzrdir, revision_id=None):
734
# TODO: Option to control what format is created?
736
dest_repo = self._real_repository._format.initialize(to_bzrdir,
738
dest_repo.fetch(self, revision_id=revision_id)
741
### These methods are just thin shims to the VFS object for now.
743
def revision_tree(self, revision_id):
745
return self._real_repository.revision_tree(revision_id)
747
def get_serializer_format(self):
749
return self._real_repository.get_serializer_format()
751
def get_commit_builder(self, branch, parents, config, timestamp=None,
752
timezone=None, committer=None, revprops=None,
754
# FIXME: It ought to be possible to call this without immediately
755
# triggering _ensure_real. For now it's the easiest thing to do.
757
real_repo = self._real_repository
758
builder = real_repo.get_commit_builder(branch, parents,
759
config, timestamp=timestamp, timezone=timezone,
760
committer=committer, revprops=revprops, revision_id=revision_id)
763
def add_fallback_repository(self, repository):
764
"""Add a repository to use for looking up data not held locally.
766
:param repository: A repository.
768
# XXX: At the moment the RemoteRepository will allow fallbacks
769
# unconditionally - however, a _real_repository will usually exist,
770
# and may raise an error if it's not accommodated by the underlying
771
# format. Eventually we should check when opening the repository
772
# whether it's willing to allow them or not.
774
# We need to accumulate additional repositories here, to pass them in
776
self._fallback_repositories.append(repository)
777
# They are also seen by the fallback repository. If it doesn't exist
778
# yet they'll be added then. This implicitly copies them.
781
def add_inventory(self, revid, inv, parents):
783
return self._real_repository.add_inventory(revid, inv, parents)
785
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
788
return self._real_repository.add_inventory_by_delta(basis_revision_id,
789
delta, new_revision_id, parents)
791
def add_revision(self, rev_id, rev, inv=None, config=None):
793
return self._real_repository.add_revision(
794
rev_id, rev, inv=inv, config=config)
797
def get_inventory(self, revision_id):
799
return self._real_repository.get_inventory(revision_id)
801
def iter_inventories(self, revision_ids):
803
return self._real_repository.iter_inventories(revision_ids)
806
def get_revision(self, revision_id):
808
return self._real_repository.get_revision(revision_id)
810
def get_transaction(self):
812
return self._real_repository.get_transaction()
815
def clone(self, a_bzrdir, revision_id=None):
817
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
819
def make_working_trees(self):
820
"""See Repository.make_working_trees"""
822
return self._real_repository.make_working_trees()
824
def revision_ids_to_search_result(self, result_set):
825
"""Convert a set of revision ids to a graph SearchResult."""
826
result_parents = set()
827
for parents in self.get_graph().get_parent_map(
828
result_set).itervalues():
829
result_parents.update(parents)
830
included_keys = result_set.intersection(result_parents)
831
start_keys = result_set.difference(included_keys)
832
exclude_keys = result_parents.difference(result_set)
833
result = graph.SearchResult(start_keys, exclude_keys,
834
len(result_set), result_set)
838
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
839
"""Return the revision ids that other has that this does not.
841
These are returned in topological order.
843
revision_id: only return revision ids included by revision_id.
845
return repository.InterRepository.get(
846
other, self).search_missing_revision_ids(revision_id, find_ghosts)
848
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
849
# Not delegated to _real_repository so that InterRepository.get has a
850
# chance to find an InterRepository specialised for RemoteRepository.
851
if self.has_same_location(source):
852
# check that last_revision is in 'from' and then return a
854
if (revision_id is not None and
855
not revision.is_null(revision_id)):
856
self.get_revision(revision_id)
858
inter = repository.InterRepository.get(source, self)
860
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
861
except NotImplementedError:
862
raise errors.IncompatibleRepositories(source, self)
864
def create_bundle(self, target, base, fileobj, format=None):
866
self._real_repository.create_bundle(target, base, fileobj, format)
869
def get_ancestry(self, revision_id, topo_sorted=True):
871
return self._real_repository.get_ancestry(revision_id, topo_sorted)
873
def fileids_altered_by_revision_ids(self, revision_ids):
875
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
877
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
879
return self._real_repository._get_versioned_file_checker(
880
revisions, revision_versions_cache)
882
def iter_files_bytes(self, desired_files):
883
"""See Repository.iter_file_bytes.
886
return self._real_repository.iter_files_bytes(desired_files)
889
def _fetch_order(self):
890
"""Decorate the real repository for now.
892
In the long term getting this back from the remote repository as part
893
of open would be more efficient.
896
return self._real_repository._fetch_order
899
def _fetch_uses_deltas(self):
900
"""Decorate the real repository for now.
902
In the long term getting this back from the remote repository as part
903
of open would be more efficient.
906
return self._real_repository._fetch_uses_deltas
909
def _fetch_reconcile(self):
910
"""Decorate the real repository for now.
912
In the long term getting this back from the remote repository as part
913
of open would be more efficient.
916
return self._real_repository._fetch_reconcile
918
def get_parent_map(self, revision_ids):
919
"""See bzrlib.Graph.get_parent_map()."""
920
return self._make_parents_provider().get_parent_map(revision_ids)
922
def _get_parent_map_rpc(self, keys):
923
"""Helper for get_parent_map that performs the RPC."""
924
medium = self._client._medium
925
if medium._is_remote_before((1, 2)):
926
# We already found out that the server can't understand
927
# Repository.get_parent_map requests, so just fetch the whole
929
# XXX: Note that this will issue a deprecation warning. This is ok
930
# :- its because we're working with a deprecated server anyway, and
931
# the user will almost certainly have seen a warning about the
932
# server version already.
933
rg = self.get_revision_graph()
934
# There is an api discrepency between get_parent_map and
935
# get_revision_graph. Specifically, a "key:()" pair in
936
# get_revision_graph just means a node has no parents. For
937
# "get_parent_map" it means the node is a ghost. So fix up the
938
# graph to correct this.
939
# https://bugs.launchpad.net/bzr/+bug/214894
940
# There is one other "bug" which is that ghosts in
941
# get_revision_graph() are not returned at all. But we won't worry
942
# about that for now.
943
for node_id, parent_ids in rg.iteritems():
945
rg[node_id] = (NULL_REVISION,)
946
rg[NULL_REVISION] = ()
951
raise ValueError('get_parent_map(None) is not valid')
952
if NULL_REVISION in keys:
953
keys.discard(NULL_REVISION)
954
found_parents = {NULL_REVISION:()}
959
# TODO(Needs analysis): We could assume that the keys being requested
960
# from get_parent_map are in a breadth first search, so typically they
961
# will all be depth N from some common parent, and we don't have to
962
# have the server iterate from the root parent, but rather from the
963
# keys we're searching; and just tell the server the keyspace we
964
# already have; but this may be more traffic again.
966
# Transform self._parents_map into a search request recipe.
967
# TODO: Manage this incrementally to avoid covering the same path
968
# repeatedly. (The server will have to on each request, but the less
969
# work done the better).
970
parents_map = self._unstacked_provider.get_cached_map()
971
if parents_map is None:
972
# Repository is not locked, so there's no cache.
974
start_set = set(parents_map)
975
result_parents = set()
976
for parents in parents_map.itervalues():
977
result_parents.update(parents)
978
stop_keys = result_parents.difference(start_set)
979
included_keys = start_set.intersection(result_parents)
980
start_set.difference_update(included_keys)
981
recipe = (start_set, stop_keys, len(parents_map))
982
body = self._serialise_search_recipe(recipe)
983
path = self.bzrdir._path_for_remote_call(self._client)
985
if type(key) is not str:
987
"key %r not a plain string" % (key,))
988
verb = 'Repository.get_parent_map'
989
args = (path,) + tuple(keys)
991
response = self._call_with_body_bytes_expecting_body(
993
except errors.UnknownSmartMethod:
994
# Server does not support this method, so get the whole graph.
995
# Worse, we have to force a disconnection, because the server now
996
# doesn't realise it has a body on the wire to consume, so the
997
# only way to recover is to abandon the connection.
999
'Server is too old for fast get_parent_map, reconnecting. '
1000
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1002
# To avoid having to disconnect repeatedly, we keep track of the
1003
# fact the server doesn't understand remote methods added in 1.2.
1004
medium._remember_remote_is_before((1, 2))
1005
return self.get_revision_graph(None)
1006
response_tuple, response_handler = response
1007
if response_tuple[0] not in ['ok']:
1008
response_handler.cancel_read_body()
1009
raise errors.UnexpectedSmartServerResponse(response_tuple)
1010
if response_tuple[0] == 'ok':
1011
coded = bz2.decompress(response_handler.read_body_bytes())
1013
# no revisions found
1015
lines = coded.split('\n')
1018
d = tuple(line.split())
1020
revision_graph[d[0]] = d[1:]
1022
# No parents - so give the Graph result (NULL_REVISION,).
1023
revision_graph[d[0]] = (NULL_REVISION,)
1024
return revision_graph
1027
def get_signature_text(self, revision_id):
1029
return self._real_repository.get_signature_text(revision_id)
1032
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1033
def get_revision_graph_with_ghosts(self, revision_ids=None):
1035
return self._real_repository.get_revision_graph_with_ghosts(
1036
revision_ids=revision_ids)
1039
def get_inventory_xml(self, revision_id):
1041
return self._real_repository.get_inventory_xml(revision_id)
1043
def deserialise_inventory(self, revision_id, xml):
1045
return self._real_repository.deserialise_inventory(revision_id, xml)
1047
def reconcile(self, other=None, thorough=False):
1049
return self._real_repository.reconcile(other=other, thorough=thorough)
1051
def all_revision_ids(self):
1053
return self._real_repository.all_revision_ids()
1056
def get_deltas_for_revisions(self, revisions):
1058
return self._real_repository.get_deltas_for_revisions(revisions)
1061
def get_revision_delta(self, revision_id):
1063
return self._real_repository.get_revision_delta(revision_id)
1066
def revision_trees(self, revision_ids):
1068
return self._real_repository.revision_trees(revision_ids)
1071
def get_revision_reconcile(self, revision_id):
1073
return self._real_repository.get_revision_reconcile(revision_id)
1076
def check(self, revision_ids=None):
1078
return self._real_repository.check(revision_ids=revision_ids)
1080
def copy_content_into(self, destination, revision_id=None):
1082
return self._real_repository.copy_content_into(
1083
destination, revision_id=revision_id)
1085
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1086
# get a tarball of the remote repository, and copy from that into the
1088
from bzrlib import osutils
1090
# TODO: Maybe a progress bar while streaming the tarball?
1091
note("Copying repository content as tarball...")
1092
tar_file = self._get_tarball('bz2')
1093
if tar_file is None:
1095
destination = to_bzrdir.create_repository()
1097
tar = tarfile.open('repository', fileobj=tar_file,
1099
tmpdir = osutils.mkdtemp()
1101
_extract_tar(tar, tmpdir)
1102
tmp_bzrdir = BzrDir.open(tmpdir)
1103
tmp_repo = tmp_bzrdir.open_repository()
1104
tmp_repo.copy_content_into(destination, revision_id)
1106
osutils.rmtree(tmpdir)
1110
# TODO: Suggestion from john: using external tar is much faster than
1111
# python's tarfile library, but it may not work on windows.
1114
def inventories(self):
1115
"""Decorate the real repository for now.
1117
In the long term a full blown network facility is needed to
1118
avoid creating a real repository object locally.
1121
return self._real_repository.inventories
1125
"""Compress the data within the repository.
1127
This is not currently implemented within the smart server.
1130
return self._real_repository.pack()
1133
def revisions(self):
1134
"""Decorate the real repository for now.
1136
In the short term this should become a real object to intercept graph
1139
In the long term a full blown network facility is needed.
1142
return self._real_repository.revisions
1144
def set_make_working_trees(self, new_value):
1146
self._real_repository.set_make_working_trees(new_value)
1149
def signatures(self):
1150
"""Decorate the real repository for now.
1152
In the long term a full blown network facility is needed to avoid
1153
creating a real repository object locally.
1156
return self._real_repository.signatures
1159
def sign_revision(self, revision_id, gpg_strategy):
1161
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1165
"""Decorate the real repository for now.
1167
In the long term a full blown network facility is needed to avoid
1168
creating a real repository object locally.
1171
return self._real_repository.texts
1174
def get_revisions(self, revision_ids):
1176
return self._real_repository.get_revisions(revision_ids)
1178
def supports_rich_root(self):
1180
return self._real_repository.supports_rich_root()
1182
def iter_reverse_revision_history(self, revision_id):
1184
return self._real_repository.iter_reverse_revision_history(revision_id)
1187
def _serializer(self):
1189
return self._real_repository._serializer
1191
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1193
return self._real_repository.store_revision_signature(
1194
gpg_strategy, plaintext, revision_id)
1196
def add_signature_text(self, revision_id, signature):
1198
return self._real_repository.add_signature_text(revision_id, signature)
1200
def has_signature_for_revision_id(self, revision_id):
1202
return self._real_repository.has_signature_for_revision_id(revision_id)
1204
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1206
return self._real_repository.item_keys_introduced_by(revision_ids,
1207
_files_pb=_files_pb)
1209
def revision_graph_can_have_wrong_parents(self):
1210
# The answer depends on the remote repo format.
1212
return self._real_repository.revision_graph_can_have_wrong_parents()
1214
def _find_inconsistent_revision_parents(self):
1216
return self._real_repository._find_inconsistent_revision_parents()
1218
def _check_for_inconsistent_revision_parents(self):
1220
return self._real_repository._check_for_inconsistent_revision_parents()
1222
def _make_parents_provider(self, other=None):
1223
providers = [self._unstacked_provider]
1224
if other is not None:
1225
providers.insert(0, other)
1226
providers.extend(r._make_parents_provider() for r in
1227
self._fallback_repositories)
1228
return graph._StackedParentsProvider(providers)
1230
def _serialise_search_recipe(self, recipe):
1231
"""Serialise a graph search recipe.
1233
:param recipe: A search recipe (start, stop, count).
1234
:return: Serialised bytes.
1236
start_keys = ' '.join(recipe[0])
1237
stop_keys = ' '.join(recipe[1])
1238
count = str(recipe[2])
1239
return '\n'.join((start_keys, stop_keys, count))
1242
path = self.bzrdir._path_for_remote_call(self._client)
1244
response = self._call('PackRepository.autopack', path)
1245
except errors.UnknownSmartMethod:
1247
self._real_repository._pack_collection.autopack()
1249
if self._real_repository is not None:
1250
# Reset the real repository's cache of pack names.
1251
# XXX: At some point we may be able to skip this and just rely on
1252
# the automatic retry logic to do the right thing, but for now we
1253
# err on the side of being correct rather than being optimal.
1254
self._real_repository._pack_collection.reload_pack_names()
1255
if response[0] != 'ok':
1256
raise errors.UnexpectedSmartServerResponse(response)
1259
class RemoteBranchLockableFiles(LockableFiles):
1260
"""A 'LockableFiles' implementation that talks to a smart server.
1262
This is not a public interface class.
1265
def __init__(self, bzrdir, _client):
1266
self.bzrdir = bzrdir
1267
self._client = _client
1268
self._need_find_modes = True
1269
LockableFiles.__init__(
1270
self, bzrdir.get_branch_transport(None),
1271
'lock', lockdir.LockDir)
1273
def _find_modes(self):
1274
# RemoteBranches don't let the client set the mode of control files.
1275
self._dir_mode = None
1276
self._file_mode = None
1279
class RemoteBranchFormat(branch.BranchFormat):
1282
super(RemoteBranchFormat, self).__init__()
1283
self._matchingbzrdir = RemoteBzrDirFormat()
1284
self._matchingbzrdir.set_branch_format(self)
1286
def __eq__(self, other):
1287
return (isinstance(other, RemoteBranchFormat) and
1288
self.__dict__ == other.__dict__)
1290
def get_format_description(self):
1291
return 'Remote BZR Branch'
1293
def get_format_string(self):
1294
return 'Remote BZR Branch'
1296
def open(self, a_bzrdir):
1297
return a_bzrdir.open_branch()
1299
def initialize(self, a_bzrdir):
1300
# Delegate to a _real object here - the RemoteBzrDir format now
1301
# supports delegating to parameterised branch formats and as such
1302
# this RemoteBranchFormat method is only called when no specific format
1304
if not isinstance(a_bzrdir, RemoteBzrDir):
1305
result = a_bzrdir.create_branch()
1307
a_bzrdir._ensure_real()
1308
result = a_bzrdir._real_bzrdir.create_branch()
1309
if not isinstance(result, RemoteBranch):
1310
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1313
def supports_tags(self):
1314
# Remote branches might support tags, but we won't know until we
1315
# access the real remote branch.
1319
class RemoteBranch(branch.Branch, _RpcHelper):
1320
"""Branch stored on a server accessed by HPSS RPC.
1322
At the moment most operations are mapped down to simple file operations.
1325
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1327
"""Create a RemoteBranch instance.
1329
:param real_branch: An optional local implementation of the branch
1330
format, usually accessing the data via the VFS.
1331
:param _client: Private parameter for testing.
1333
# We intentionally don't call the parent class's __init__, because it
1334
# will try to assign to self.tags, which is a property in this subclass.
1335
# And the parent's __init__ doesn't do much anyway.
1336
self._revision_id_to_revno_cache = None
1337
self._partial_revision_id_to_revno_cache = {}
1338
self._revision_history_cache = None
1339
self._last_revision_info_cache = None
1340
self._merge_sorted_revisions_cache = None
1341
self.bzrdir = remote_bzrdir
1342
if _client is not None:
1343
self._client = _client
1345
self._client = remote_bzrdir._client
1346
self.repository = remote_repository
1347
if real_branch is not None:
1348
self._real_branch = real_branch
1349
# Give the remote repository the matching real repo.
1350
real_repo = self._real_branch.repository
1351
if isinstance(real_repo, RemoteRepository):
1352
real_repo._ensure_real()
1353
real_repo = real_repo._real_repository
1354
self.repository._set_real_repository(real_repo)
1355
# Give the branch the remote repository to let fast-pathing happen.
1356
self._real_branch.repository = self.repository
1358
self._real_branch = None
1359
# Fill out expected attributes of branch for bzrlib api users.
1360
self._format = RemoteBranchFormat()
1361
self.base = self.bzrdir.root_transport.base
1362
self._control_files = None
1363
self._lock_mode = None
1364
self._lock_token = None
1365
self._repo_lock_token = None
1366
self._lock_count = 0
1367
self._leave_lock = False
1368
# The base class init is not called, so we duplicate this:
1369
hooks = branch.Branch.hooks['open']
1372
self._setup_stacking()
1374
def _setup_stacking(self):
1375
# configure stacking into the remote repository, by reading it from
1378
fallback_url = self.get_stacked_on_url()
1379
except (errors.NotStacked, errors.UnstackableBranchFormat,
1380
errors.UnstackableRepositoryFormat), e:
1382
# it's relative to this branch...
1383
fallback_url = urlutils.join(self.base, fallback_url)
1384
transports = [self.bzrdir.root_transport]
1385
if self._real_branch is not None:
1386
transports.append(self._real_branch._transport)
1387
stacked_on = branch.Branch.open(fallback_url,
1388
possible_transports=transports)
1389
self.repository.add_fallback_repository(stacked_on.repository)
1391
def _get_real_transport(self):
1392
# if we try vfs access, return the real branch's vfs transport
1394
return self._real_branch._transport
1396
_transport = property(_get_real_transport)
1399
return "%s(%s)" % (self.__class__.__name__, self.base)
1403
def _ensure_real(self):
1404
"""Ensure that there is a _real_branch set.
1406
Used before calls to self._real_branch.
1408
if self._real_branch is None:
1409
if not vfs.vfs_enabled():
1410
raise AssertionError('smart server vfs must be enabled '
1411
'to use vfs implementation')
1412
self.bzrdir._ensure_real()
1413
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1414
if self.repository._real_repository is None:
1415
# Give the remote repository the matching real repo.
1416
real_repo = self._real_branch.repository
1417
if isinstance(real_repo, RemoteRepository):
1418
real_repo._ensure_real()
1419
real_repo = real_repo._real_repository
1420
self.repository._set_real_repository(real_repo)
1421
# Give the real branch the remote repository to let fast-pathing
1423
self._real_branch.repository = self.repository
1424
if self._lock_mode == 'r':
1425
self._real_branch.lock_read()
1426
elif self._lock_mode == 'w':
1427
self._real_branch.lock_write(token=self._lock_token)
1429
def _translate_error(self, err, **context):
1430
self.repository._translate_error(err, branch=self, **context)
1432
def _clear_cached_state(self):
1433
super(RemoteBranch, self)._clear_cached_state()
1434
if self._real_branch is not None:
1435
self._real_branch._clear_cached_state()
1437
def _clear_cached_state_of_remote_branch_only(self):
1438
"""Like _clear_cached_state, but doesn't clear the cache of
1441
This is useful when falling back to calling a method of
1442
self._real_branch that changes state. In that case the underlying
1443
branch changes, so we need to invalidate this RemoteBranch's cache of
1444
it. However, there's no need to invalidate the _real_branch's cache
1445
too, in fact doing so might harm performance.
1447
super(RemoteBranch, self)._clear_cached_state()
1450
def control_files(self):
1451
# Defer actually creating RemoteBranchLockableFiles until its needed,
1452
# because it triggers an _ensure_real that we otherwise might not need.
1453
if self._control_files is None:
1454
self._control_files = RemoteBranchLockableFiles(
1455
self.bzrdir, self._client)
1456
return self._control_files
1458
def _get_checkout_format(self):
1460
return self._real_branch._get_checkout_format()
1462
def get_physical_lock_status(self):
1463
"""See Branch.get_physical_lock_status()."""
1464
# should be an API call to the server, as branches must be lockable.
1466
return self._real_branch.get_physical_lock_status()
1468
def get_stacked_on_url(self):
1469
"""Get the URL this branch is stacked against.
1471
:raises NotStacked: If the branch is not stacked.
1472
:raises UnstackableBranchFormat: If the branch does not support
1474
:raises UnstackableRepositoryFormat: If the repository does not support
1478
# there may not be a repository yet, so we can't use
1479
# self._translate_error, so we can't use self._call either.
1480
response = self._client.call('Branch.get_stacked_on_url',
1481
self._remote_path())
1482
except errors.ErrorFromSmartServer, err:
1483
# there may not be a repository yet, so we can't call through
1484
# its _translate_error
1485
_translate_error(err, branch=self)
1486
except errors.UnknownSmartMethod, err:
1488
return self._real_branch.get_stacked_on_url()
1489
if response[0] != 'ok':
1490
raise errors.UnexpectedSmartServerResponse(response)
1493
def lock_read(self):
1494
self.repository.lock_read()
1495
if not self._lock_mode:
1496
self._lock_mode = 'r'
1497
self._lock_count = 1
1498
if self._real_branch is not None:
1499
self._real_branch.lock_read()
1501
self._lock_count += 1
1503
def _remote_lock_write(self, token):
1505
branch_token = repo_token = ''
1507
branch_token = token
1508
repo_token = self.repository.lock_write()
1509
self.repository.unlock()
1510
err_context = {'token': token}
1511
response = self._call(
1512
'Branch.lock_write', self._remote_path(), branch_token,
1513
repo_token or '', **err_context)
1514
if response[0] != 'ok':
1515
raise errors.UnexpectedSmartServerResponse(response)
1516
ok, branch_token, repo_token = response
1517
return branch_token, repo_token
1519
def lock_write(self, token=None):
1520
if not self._lock_mode:
1521
# Lock the branch and repo in one remote call.
1522
remote_tokens = self._remote_lock_write(token)
1523
self._lock_token, self._repo_lock_token = remote_tokens
1524
if not self._lock_token:
1525
raise SmartProtocolError('Remote server did not return a token!')
1526
# Tell the self.repository object that it is locked.
1527
self.repository.lock_write(
1528
self._repo_lock_token, _skip_rpc=True)
1530
if self._real_branch is not None:
1531
self._real_branch.lock_write(token=self._lock_token)
1532
if token is not None:
1533
self._leave_lock = True
1535
self._leave_lock = False
1536
self._lock_mode = 'w'
1537
self._lock_count = 1
1538
elif self._lock_mode == 'r':
1539
raise errors.ReadOnlyTransaction
1541
if token is not None:
1542
# A token was given to lock_write, and we're relocking, so
1543
# check that the given token actually matches the one we
1545
if token != self._lock_token:
1546
raise errors.TokenMismatch(token, self._lock_token)
1547
self._lock_count += 1
1548
# Re-lock the repository too.
1549
self.repository.lock_write(self._repo_lock_token)
1550
return self._lock_token or None
1552
def _unlock(self, branch_token, repo_token):
1553
err_context = {'token': str((branch_token, repo_token))}
1554
response = self._call(
1555
'Branch.unlock', self._remote_path(), branch_token,
1556
repo_token or '', **err_context)
1557
if response == ('ok',):
1559
raise errors.UnexpectedSmartServerResponse(response)
1563
self._lock_count -= 1
1564
if not self._lock_count:
1565
self._clear_cached_state()
1566
mode = self._lock_mode
1567
self._lock_mode = None
1568
if self._real_branch is not None:
1569
if (not self._leave_lock and mode == 'w' and
1570
self._repo_lock_token):
1571
# If this RemoteBranch will remove the physical lock
1572
# for the repository, make sure the _real_branch
1573
# doesn't do it first. (Because the _real_branch's
1574
# repository is set to be the RemoteRepository.)
1575
self._real_branch.repository.leave_lock_in_place()
1576
self._real_branch.unlock()
1578
# Only write-locked branched need to make a remote method
1579
# call to perfom the unlock.
1581
if not self._lock_token:
1582
raise AssertionError('Locked, but no token!')
1583
branch_token = self._lock_token
1584
repo_token = self._repo_lock_token
1585
self._lock_token = None
1586
self._repo_lock_token = None
1587
if not self._leave_lock:
1588
self._unlock(branch_token, repo_token)
1590
self.repository.unlock()
1592
def break_lock(self):
1594
return self._real_branch.break_lock()
1596
def leave_lock_in_place(self):
1597
if not self._lock_token:
1598
raise NotImplementedError(self.leave_lock_in_place)
1599
self._leave_lock = True
1601
def dont_leave_lock_in_place(self):
1602
if not self._lock_token:
1603
raise NotImplementedError(self.dont_leave_lock_in_place)
1604
self._leave_lock = False
1606
def _last_revision_info(self):
1607
response = self._call('Branch.last_revision_info', self._remote_path())
1608
if response[0] != 'ok':
1609
raise SmartProtocolError('unexpected response code %s' % (response,))
1610
revno = int(response[1])
1611
last_revision = response[2]
1612
return (revno, last_revision)
1614
def _gen_revision_history(self):
1615
"""See Branch._gen_revision_history()."""
1616
response_tuple, response_handler = self._call_expecting_body(
1617
'Branch.revision_history', self._remote_path())
1618
if response_tuple[0] != 'ok':
1619
raise errors.UnexpectedSmartServerResponse(response_tuple)
1620
result = response_handler.read_body_bytes().split('\x00')
1625
def _remote_path(self):
1626
return self.bzrdir._path_for_remote_call(self._client)
1628
def _set_last_revision_descendant(self, revision_id, other_branch,
1629
allow_diverged=False, allow_overwrite_descendant=False):
1630
# This performs additional work to meet the hook contract; while its
1631
# undesirable, we have to synthesise the revno to call the hook, and
1632
# not calling the hook is worse as it means changes can't be prevented.
1633
# Having calculated this though, we can't just call into
1634
# set_last_revision_info as a simple call, because there is a set_rh
1635
# hook that some folk may still be using.
1636
old_revno, old_revid = self.last_revision_info()
1637
history = self._lefthand_history(revision_id)
1638
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1639
err_context = {'other_branch': other_branch}
1640
response = self._call('Branch.set_last_revision_ex',
1641
self._remote_path(), self._lock_token, self._repo_lock_token,
1642
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1644
self._clear_cached_state()
1645
if len(response) != 3 and response[0] != 'ok':
1646
raise errors.UnexpectedSmartServerResponse(response)
1647
new_revno, new_revision_id = response[1:]
1648
self._last_revision_info_cache = new_revno, new_revision_id
1649
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1650
if self._real_branch is not None:
1651
cache = new_revno, new_revision_id
1652
self._real_branch._last_revision_info_cache = cache
1654
def _set_last_revision(self, revision_id):
1655
old_revno, old_revid = self.last_revision_info()
1656
# This performs additional work to meet the hook contract; while its
1657
# undesirable, we have to synthesise the revno to call the hook, and
1658
# not calling the hook is worse as it means changes can't be prevented.
1659
# Having calculated this though, we can't just call into
1660
# set_last_revision_info as a simple call, because there is a set_rh
1661
# hook that some folk may still be using.
1662
history = self._lefthand_history(revision_id)
1663
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1664
self._clear_cached_state()
1665
response = self._call('Branch.set_last_revision',
1666
self._remote_path(), self._lock_token, self._repo_lock_token,
1668
if response != ('ok',):
1669
raise errors.UnexpectedSmartServerResponse(response)
1670
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1673
def set_revision_history(self, rev_history):
1674
# Send just the tip revision of the history; the server will generate
1675
# the full history from that. If the revision doesn't exist in this
1676
# branch, NoSuchRevision will be raised.
1677
if rev_history == []:
1680
rev_id = rev_history[-1]
1681
self._set_last_revision(rev_id)
1682
for hook in branch.Branch.hooks['set_rh']:
1683
hook(self, rev_history)
1684
self._cache_revision_history(rev_history)
1686
def get_parent(self):
1688
return self._real_branch.get_parent()
1690
def _get_parent_location(self):
1691
# Used by tests, when checking normalisation of given vs stored paths.
1693
return self._real_branch._get_parent_location()
1695
def set_parent(self, url):
1697
return self._real_branch.set_parent(url)
1699
def _set_parent_location(self, url):
1700
# Used by tests, to poke bad urls into branch configurations
1702
self.set_parent(url)
1705
return self._real_branch._set_parent_location(url)
1707
def set_stacked_on_url(self, stacked_location):
1708
"""Set the URL this branch is stacked against.
1710
:raises UnstackableBranchFormat: If the branch does not support
1712
:raises UnstackableRepositoryFormat: If the repository does not support
1716
return self._real_branch.set_stacked_on_url(stacked_location)
1718
def sprout(self, to_bzrdir, revision_id=None):
1719
branch_format = to_bzrdir._format._branch_format
1720
if (branch_format is None or
1721
isinstance(branch_format, RemoteBranchFormat)):
1722
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1723
# implies the same thing), but RemoteBranches can't be created at
1724
# arbitrary URLs. So create a branch in the same format as
1725
# _real_branch instead.
1726
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1727
# to_bzrdir.create_branch to create a RemoteBranch after all...
1729
result = self._real_branch._format.initialize(to_bzrdir)
1730
self.copy_content_into(result, revision_id=revision_id)
1731
result.set_parent(self.bzrdir.root_transport.base)
1733
result = branch.Branch.sprout(
1734
self, to_bzrdir, revision_id=revision_id)
1738
def pull(self, source, overwrite=False, stop_revision=None,
1740
self._clear_cached_state_of_remote_branch_only()
1742
return self._real_branch.pull(
1743
source, overwrite=overwrite, stop_revision=stop_revision,
1744
_override_hook_target=self, **kwargs)
1747
def push(self, target, overwrite=False, stop_revision=None):
1749
return self._real_branch.push(
1750
target, overwrite=overwrite, stop_revision=stop_revision,
1751
_override_hook_source_branch=self)
1753
def is_locked(self):
1754
return self._lock_count >= 1
1757
def revision_id_to_revno(self, revision_id):
1759
return self._real_branch.revision_id_to_revno(revision_id)
1762
def set_last_revision_info(self, revno, revision_id):
1763
# XXX: These should be returned by the set_last_revision_info verb
1764
old_revno, old_revid = self.last_revision_info()
1765
self._run_pre_change_branch_tip_hooks(revno, revision_id)
1766
revision_id = ensure_null(revision_id)
1768
response = self._call('Branch.set_last_revision_info',
1769
self._remote_path(), self._lock_token, self._repo_lock_token,
1770
str(revno), revision_id)
1771
except errors.UnknownSmartMethod:
1773
self._clear_cached_state_of_remote_branch_only()
1774
self._real_branch.set_last_revision_info(revno, revision_id)
1775
self._last_revision_info_cache = revno, revision_id
1777
if response == ('ok',):
1778
self._clear_cached_state()
1779
self._last_revision_info_cache = revno, revision_id
1780
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1781
# Update the _real_branch's cache too.
1782
if self._real_branch is not None:
1783
cache = self._last_revision_info_cache
1784
self._real_branch._last_revision_info_cache = cache
1786
raise errors.UnexpectedSmartServerResponse(response)
1789
def generate_revision_history(self, revision_id, last_rev=None,
1791
medium = self._client._medium
1792
if not medium._is_remote_before((1, 6)):
1793
# Use a smart method for 1.6 and above servers
1795
self._set_last_revision_descendant(revision_id, other_branch,
1796
allow_diverged=True, allow_overwrite_descendant=True)
1798
except errors.UnknownSmartMethod:
1799
medium._remember_remote_is_before((1, 6))
1800
self._clear_cached_state_of_remote_branch_only()
1801
self.set_revision_history(self._lefthand_history(revision_id,
1802
last_rev=last_rev,other_branch=other_branch))
1807
return self._real_branch.tags
1809
def set_push_location(self, location):
1811
return self._real_branch.set_push_location(location)
1814
def update_revisions(self, other, stop_revision=None, overwrite=False,
1816
"""See Branch.update_revisions."""
1819
if stop_revision is None:
1820
stop_revision = other.last_revision()
1821
if revision.is_null(stop_revision):
1822
# if there are no commits, we're done.
1824
self.fetch(other, stop_revision)
1827
# Just unconditionally set the new revision. We don't care if
1828
# the branches have diverged.
1829
self._set_last_revision(stop_revision)
1831
medium = self._client._medium
1832
if not medium._is_remote_before((1, 6)):
1834
self._set_last_revision_descendant(stop_revision, other)
1836
except errors.UnknownSmartMethod:
1837
medium._remember_remote_is_before((1, 6))
1838
# Fallback for pre-1.6 servers: check for divergence
1839
# client-side, then do _set_last_revision.
1840
last_rev = revision.ensure_null(self.last_revision())
1842
graph = self.repository.get_graph()
1843
if self._check_if_descendant_or_diverged(
1844
stop_revision, last_rev, graph, other):
1845
# stop_revision is a descendant of last_rev, but we aren't
1846
# overwriting, so we're done.
1848
self._set_last_revision(stop_revision)
1853
def _extract_tar(tar, to_dir):
1854
"""Extract all the contents of a tarfile object.
1856
A replacement for extractall, which is not present in python2.4
1859
tar.extract(tarinfo, to_dir)
1862
def _translate_error(err, **context):
1863
"""Translate an ErrorFromSmartServer into a more useful error.
1865
Possible context keys:
1873
If the error from the server doesn't match a known pattern, then
1874
UnknownErrorFromSmartServer is raised.
1878
return context[name]
1879
except KeyError, key_err:
1880
mutter('Missing key %r in context %r', key_err.args[0], context)
1883
"""Get the path from the context if present, otherwise use first error
1887
return context['path']
1888
except KeyError, key_err:
1890
return err.error_args[0]
1891
except IndexError, idx_err:
1893
'Missing key %r in context %r', key_err.args[0], context)
1896
if err.error_verb == 'NoSuchRevision':
1897
raise NoSuchRevision(find('branch'), err.error_args[0])
1898
elif err.error_verb == 'nosuchrevision':
1899
raise NoSuchRevision(find('repository'), err.error_args[0])
1900
elif err.error_tuple == ('nobranch',):
1901
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1902
elif err.error_verb == 'norepository':
1903
raise errors.NoRepositoryPresent(find('bzrdir'))
1904
elif err.error_verb == 'LockContention':
1905
raise errors.LockContention('(remote lock)')
1906
elif err.error_verb == 'UnlockableTransport':
1907
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1908
elif err.error_verb == 'LockFailed':
1909
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1910
elif err.error_verb == 'TokenMismatch':
1911
raise errors.TokenMismatch(find('token'), '(remote token)')
1912
elif err.error_verb == 'Diverged':
1913
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1914
elif err.error_verb == 'TipChangeRejected':
1915
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1916
elif err.error_verb == 'UnstackableBranchFormat':
1917
raise errors.UnstackableBranchFormat(*err.error_args)
1918
elif err.error_verb == 'UnstackableRepositoryFormat':
1919
raise errors.UnstackableRepositoryFormat(*err.error_args)
1920
elif err.error_verb == 'NotStacked':
1921
raise errors.NotStacked(branch=find('branch'))
1922
elif err.error_verb == 'PermissionDenied':
1924
if len(err.error_args) >= 2:
1925
extra = err.error_args[1]
1928
raise errors.PermissionDenied(path, extra=extra)
1929
elif err.error_verb == 'ReadError':
1931
raise errors.ReadError(path)
1932
elif err.error_verb == 'NoSuchFile':
1934
raise errors.NoSuchFile(path)
1935
elif err.error_verb == 'FileExists':
1936
raise errors.FileExists(err.error_args[0])
1937
elif err.error_verb == 'DirectoryNotEmpty':
1938
raise errors.DirectoryNotEmpty(err.error_args[0])
1939
elif err.error_verb == 'ShortReadvError':
1940
args = err.error_args
1941
raise errors.ShortReadvError(
1942
args[0], int(args[1]), int(args[2]), int(args[3]))
1943
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1944
encoding = str(err.error_args[0]) # encoding must always be a string
1945
val = err.error_args[1]
1946
start = int(err.error_args[2])
1947
end = int(err.error_args[3])
1948
reason = str(err.error_args[4]) # reason must always be a string
1949
if val.startswith('u:'):
1950
val = val[2:].decode('utf-8')
1951
elif val.startswith('s:'):
1952
val = val[2:].decode('base64')
1953
if err.error_verb == 'UnicodeDecodeError':
1954
raise UnicodeDecodeError(encoding, val, start, end, reason)
1955
elif err.error_verb == 'UnicodeEncodeError':
1956
raise UnicodeEncodeError(encoding, val, start, end, reason)
1957
elif err.error_verb == 'ReadOnlyError':
1958
raise errors.TransportNotPossible('readonly transport')
1959
raise errors.UnknownErrorFromSmartServer(err)