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
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
65
return self._client.call_with_body_bytes_expecting_body(
66
method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
# Note: RemoteBzrDirFormat is in bzrdir.py
72
class RemoteBzrDir(BzrDir, _RpcHelper):
73
"""Control directory on a remote server, accessed via bzr:// or similar."""
75
def __init__(self, transport, format, _client=None):
76
"""Construct a RemoteBzrDir.
78
:param _client: Private parameter for testing. Disables probing and the
81
BzrDir.__init__(self, transport, format)
82
# this object holds a delegated bzrdir that uses file-level operations
83
# to talk to the other side
84
self._real_bzrdir = None
87
medium = transport.get_smart_medium()
88
self._client = client._SmartClient(medium)
90
self._client = _client
93
path = self._path_for_remote_call(self._client)
94
response = self._call('BzrDir.open', path)
95
if response not in [('yes',), ('no',)]:
96
raise errors.UnexpectedSmartServerResponse(response)
97
if response == ('no',):
98
raise errors.NotBranchError(path=transport.base)
100
def _ensure_real(self):
101
"""Ensure that there is a _real_bzrdir set.
103
Used before calls to self._real_bzrdir.
105
if not self._real_bzrdir:
106
self._real_bzrdir = BzrDir.open_from_transport(
107
self.root_transport, _server_formats=False)
109
def _translate_error(self, err, **context):
110
_translate_error(err, bzrdir=self, **context)
112
def cloning_metadir(self, stacked=False):
114
return self._real_bzrdir.cloning_metadir(stacked)
116
def create_repository(self, shared=False):
117
# as per meta1 formats - just delegate to the format object which may
119
result = self._format.repository_format.initialize(self, shared)
120
if not isinstance(result, RemoteRepository):
121
return self.open_repository()
125
def destroy_repository(self):
126
"""See BzrDir.destroy_repository"""
128
self._real_bzrdir.destroy_repository()
130
def create_branch(self):
131
# as per meta1 formats - just delegate to the format object which may
133
real_branch = self._format.get_branch_format().initialize(self)
134
if not isinstance(real_branch, RemoteBranch):
135
return RemoteBranch(self, self.find_repository(), real_branch)
139
def destroy_branch(self):
140
"""See BzrDir.destroy_branch"""
142
self._real_bzrdir.destroy_branch()
144
def create_workingtree(self, revision_id=None, from_branch=None):
145
raise errors.NotLocalUrl(self.transport.base)
147
def find_branch_format(self):
148
"""Find the branch 'format' for this bzrdir.
150
This might be a synthetic object for e.g. RemoteBranch and SVN.
152
b = self.open_branch()
155
def get_branch_reference(self):
156
"""See BzrDir.get_branch_reference()."""
157
path = self._path_for_remote_call(self._client)
158
response = self._call('BzrDir.open_branch', path)
159
if response[0] == 'ok':
160
if response[1] == '':
161
# branch at this location.
164
# a branch reference, use the existing BranchReference logic.
167
raise errors.UnexpectedSmartServerResponse(response)
169
def _get_tree_branch(self):
170
"""See BzrDir._get_tree_branch()."""
171
return None, self.open_branch()
173
def open_branch(self, _unsupported=False):
175
raise NotImplementedError('unsupported flag support not implemented yet.')
176
reference_url = self.get_branch_reference()
177
if reference_url is None:
178
# branch at this location.
179
return RemoteBranch(self, self.find_repository())
181
# a branch reference, use the existing BranchReference logic.
182
format = BranchReferenceFormat()
183
return format.open(self, _found=True, location=reference_url)
185
def open_repository(self):
186
path = self._path_for_remote_call(self._client)
187
verb = 'BzrDir.find_repositoryV2'
189
response = self._call(verb, path)
190
except errors.UnknownSmartMethod:
191
verb = 'BzrDir.find_repository'
192
response = self._call(verb, path)
193
if response[0] != 'ok':
194
raise errors.UnexpectedSmartServerResponse(response)
195
if verb == 'BzrDir.find_repository':
196
# servers that don't support the V2 method don't support external
198
response = response + ('no', )
199
if not (len(response) == 5):
200
raise SmartProtocolError('incorrect response length %s' % (response,))
201
if response[1] == '':
202
format = RemoteRepositoryFormat()
203
format.rich_root_data = (response[2] == 'yes')
204
format.supports_tree_reference = (response[3] == 'yes')
205
# No wire format to check this yet.
206
format.supports_external_lookups = (response[4] == 'yes')
207
# Used to support creating a real format instance when needed.
208
format._creating_bzrdir = self
209
remote_repo = RemoteRepository(self, format)
210
format._creating_repo = remote_repo
213
raise errors.NoRepositoryPresent(self)
215
def open_workingtree(self, recommend_upgrade=True):
217
if self._real_bzrdir.has_workingtree():
218
raise errors.NotLocalUrl(self.root_transport)
220
raise errors.NoWorkingTree(self.root_transport.base)
222
def _path_for_remote_call(self, client):
223
"""Return the path to be used for this bzrdir in a remote call."""
224
return client.remote_path_from_transport(self.root_transport)
226
def get_branch_transport(self, branch_format):
228
return self._real_bzrdir.get_branch_transport(branch_format)
230
def get_repository_transport(self, repository_format):
232
return self._real_bzrdir.get_repository_transport(repository_format)
234
def get_workingtree_transport(self, workingtree_format):
236
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
238
def can_convert_format(self):
239
"""Upgrading of remote bzrdirs is not supported yet."""
242
def needs_format_conversion(self, format=None):
243
"""Upgrading of remote bzrdirs is not supported yet."""
245
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
246
% 'needs_format_conversion(format=None)')
249
def clone(self, url, revision_id=None, force_new_repo=False,
250
preserve_stacking=False):
252
return self._real_bzrdir.clone(url, revision_id=revision_id,
253
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
255
def get_config(self):
257
return self._real_bzrdir.get_config()
260
class RemoteRepositoryFormat(repository.RepositoryFormat):
261
"""Format for repositories accessed over a _SmartClient.
263
Instances of this repository are represented by RemoteRepository
266
The RemoteRepositoryFormat is parameterized during construction
267
to reflect the capabilities of the real, remote format. Specifically
268
the attributes rich_root_data and supports_tree_reference are set
269
on a per instance basis, and are not set (and should not be) at
272
:ivar _custom_format: If set, a specific concrete repository format that
273
will be used when initializing a repository with this
274
RemoteRepositoryFormat.
275
:ivar _creating_repo: If set, the repository object that this
276
RemoteRepositoryFormat was created for: it can be called into
277
to obtain data like the network name.
280
_matchingbzrdir = RemoteBzrDirFormat()
283
repository.RepositoryFormat.__init__(self)
284
self._custom_format = None
285
self._network_name = None
286
self._creating_bzrdir = None
288
def _vfs_initialize(self, a_bzrdir, shared):
289
"""Helper for common code in initialize."""
290
if self._custom_format:
291
# Custom format requested
292
result = self._custom_format.initialize(a_bzrdir, shared=shared)
293
elif self._creating_bzrdir is not None:
294
# Use the format that the repository we were created to back
296
prior_repo = self._creating_bzrdir.open_repository()
297
prior_repo._ensure_real()
298
result = prior_repo._real_repository._format.initialize(
299
a_bzrdir, shared=shared)
301
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
302
# support remote initialization.
303
# We delegate to a real object at this point (as RemoteBzrDir
304
# delegate to the repository format which would lead to infinite
305
# recursion if we just called a_bzrdir.create_repository.
306
a_bzrdir._ensure_real()
307
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
308
if not isinstance(result, RemoteRepository):
309
return self.open(a_bzrdir)
313
def initialize(self, a_bzrdir, shared=False):
314
# Being asked to create on a non RemoteBzrDir:
315
if not isinstance(a_bzrdir, RemoteBzrDir):
316
return self._vfs_initialize(a_bzrdir, shared)
317
medium = a_bzrdir._client._medium
318
if medium._is_remote_before((1, 13)):
319
return self._vfs_initialize(a_bzrdir, shared)
320
# Creating on a remote bzr dir.
321
# 1) get the network name to use.
322
if self._custom_format:
323
network_name = self._custom_format.network_name()
325
# Select the current bzrlib default and ask for that.
326
reference_bzrdir_format = bzrdir.format_registry.get('default')()
327
reference_format = reference_bzrdir_format.repository_format
328
network_name = reference_format.network_name()
329
# 2) try direct creation via RPC
330
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
331
verb = 'BzrDir.create_repository'
337
response = a_bzrdir._call(verb, path, network_name, shared_str)
338
except errors.UnknownSmartMethod:
339
# Fallback - use vfs methods
340
return self._vfs_initialize(a_bzrdir, shared)
342
# Turn the response into a RemoteRepository object.
343
format = RemoteRepositoryFormat()
344
format.rich_root_data = (response[1] == 'yes')
345
format.supports_tree_reference = (response[2] == 'yes')
346
format.supports_external_lookups = (response[3] == 'yes')
347
format._network_name = response[4]
348
# Used to support creating a real format instance when needed.
349
format._creating_bzrdir = a_bzrdir
350
remote_repo = RemoteRepository(a_bzrdir, format)
351
format._creating_repo = remote_repo
354
def open(self, a_bzrdir):
355
if not isinstance(a_bzrdir, RemoteBzrDir):
356
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
357
return a_bzrdir.open_repository()
359
def get_format_description(self):
360
return 'bzr remote repository'
362
def __eq__(self, other):
363
return self.__class__ == other.__class__
365
def check_conversion_target(self, target_format):
366
if self.rich_root_data and not target_format.rich_root_data:
367
raise errors.BadConversionTarget(
368
'Does not support rich root data.', target_format)
369
if (self.supports_tree_reference and
370
not getattr(target_format, 'supports_tree_reference', False)):
371
raise errors.BadConversionTarget(
372
'Does not support nested trees', target_format)
374
def network_name(self):
375
if self._network_name:
376
return self._network_name
377
self._creating_repo._ensure_real()
378
return self._creating_repo._real_repository._format.network_name()
381
class RemoteRepository(_RpcHelper):
382
"""Repository accessed over rpc.
384
For the moment most operations are performed using local transport-backed
388
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
389
"""Create a RemoteRepository instance.
391
:param remote_bzrdir: The bzrdir hosting this repository.
392
:param format: The RemoteFormat object to use.
393
:param real_repository: If not None, a local implementation of the
394
repository logic for the repository, usually accessing the data
396
:param _client: Private testing parameter - override the smart client
397
to be used by the repository.
400
self._real_repository = real_repository
402
self._real_repository = None
403
self.bzrdir = remote_bzrdir
405
self._client = remote_bzrdir._client
407
self._client = _client
408
self._format = format
409
self._lock_mode = None
410
self._lock_token = None
412
self._leave_lock = False
413
self._unstacked_provider = graph.CachingParentsProvider(
414
get_parent_map=self._get_parent_map_rpc)
415
self._unstacked_provider.disable_cache()
417
# These depend on the actual remote format, so force them off for
418
# maximum compatibility. XXX: In future these should depend on the
419
# remote repository instance, but this is irrelevant until we perform
420
# reconcile via an RPC call.
421
self._reconcile_does_inventory_gc = False
422
self._reconcile_fixes_text_parents = False
423
self._reconcile_backsup_inventory = False
424
self.base = self.bzrdir.transport.base
425
# Additional places to query for data.
426
self._fallback_repositories = []
429
return "%s(%s)" % (self.__class__.__name__, self.base)
433
def abort_write_group(self, suppress_errors=False):
434
"""Complete a write group on the decorated repository.
436
Smart methods peform operations in a single step so this api
437
is not really applicable except as a compatibility thunk
438
for older plugins that don't use e.g. the CommitBuilder
441
:param suppress_errors: see Repository.abort_write_group.
444
return self._real_repository.abort_write_group(
445
suppress_errors=suppress_errors)
447
def commit_write_group(self):
448
"""Complete a write group on the decorated repository.
450
Smart methods peform operations in a single step so this api
451
is not really applicable except as a compatibility thunk
452
for older plugins that don't use e.g. the CommitBuilder
456
return self._real_repository.commit_write_group()
458
def resume_write_group(self, tokens):
460
return self._real_repository.resume_write_group(tokens)
462
def suspend_write_group(self):
464
return self._real_repository.suspend_write_group()
466
def _ensure_real(self):
467
"""Ensure that there is a _real_repository set.
469
Used before calls to self._real_repository.
471
if self._real_repository is None:
472
self.bzrdir._ensure_real()
473
self._set_real_repository(
474
self.bzrdir._real_bzrdir.open_repository())
476
def _translate_error(self, err, **context):
477
self.bzrdir._translate_error(err, repository=self, **context)
479
def find_text_key_references(self):
480
"""Find the text key references within the repository.
482
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
483
revision_ids. Each altered file-ids has the exact revision_ids that
484
altered it listed explicitly.
485
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
486
to whether they were referred to by the inventory of the
487
revision_id that they contain. The inventory texts from all present
488
revision ids are assessed to generate this report.
491
return self._real_repository.find_text_key_references()
493
def _generate_text_key_index(self):
494
"""Generate a new text key index for the repository.
496
This is an expensive function that will take considerable time to run.
498
:return: A dict mapping (file_id, revision_id) tuples to a list of
499
parents, also (file_id, revision_id) tuples.
502
return self._real_repository._generate_text_key_index()
504
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
505
def get_revision_graph(self, revision_id=None):
506
"""See Repository.get_revision_graph()."""
507
return self._get_revision_graph(revision_id)
509
def _get_revision_graph(self, revision_id):
510
"""Private method for using with old (< 1.2) servers to fallback."""
511
if revision_id is None:
513
elif revision.is_null(revision_id):
516
path = self.bzrdir._path_for_remote_call(self._client)
517
response = self._call_expecting_body(
518
'Repository.get_revision_graph', path, revision_id)
519
response_tuple, response_handler = response
520
if response_tuple[0] != 'ok':
521
raise errors.UnexpectedSmartServerResponse(response_tuple)
522
coded = response_handler.read_body_bytes()
524
# no revisions in this repository!
526
lines = coded.split('\n')
529
d = tuple(line.split())
530
revision_graph[d[0]] = d[1:]
532
return revision_graph
534
def has_revision(self, revision_id):
535
"""See Repository.has_revision()."""
536
if revision_id == NULL_REVISION:
537
# The null revision is always present.
539
path = self.bzrdir._path_for_remote_call(self._client)
540
response = self._call('Repository.has_revision', path, revision_id)
541
if response[0] not in ('yes', 'no'):
542
raise errors.UnexpectedSmartServerResponse(response)
543
if response[0] == 'yes':
545
for fallback_repo in self._fallback_repositories:
546
if fallback_repo.has_revision(revision_id):
550
def has_revisions(self, revision_ids):
551
"""See Repository.has_revisions()."""
552
# FIXME: This does many roundtrips, particularly when there are
553
# fallback repositories. -- mbp 20080905
555
for revision_id in revision_ids:
556
if self.has_revision(revision_id):
557
result.add(revision_id)
560
def has_same_location(self, other):
561
return (self.__class__ == other.__class__ and
562
self.bzrdir.transport.base == other.bzrdir.transport.base)
564
def get_graph(self, other_repository=None):
565
"""Return the graph for this repository format"""
566
parents_provider = self._make_parents_provider(other_repository)
567
return graph.Graph(parents_provider)
569
def gather_stats(self, revid=None, committers=None):
570
"""See Repository.gather_stats()."""
571
path = self.bzrdir._path_for_remote_call(self._client)
572
# revid can be None to indicate no revisions, not just NULL_REVISION
573
if revid is None or revision.is_null(revid):
577
if committers is None or not committers:
578
fmt_committers = 'no'
580
fmt_committers = 'yes'
581
response_tuple, response_handler = self._call_expecting_body(
582
'Repository.gather_stats', path, fmt_revid, fmt_committers)
583
if response_tuple[0] != 'ok':
584
raise errors.UnexpectedSmartServerResponse(response_tuple)
586
body = response_handler.read_body_bytes()
588
for line in body.split('\n'):
591
key, val_text = line.split(':')
592
if key in ('revisions', 'size', 'committers'):
593
result[key] = int(val_text)
594
elif key in ('firstrev', 'latestrev'):
595
values = val_text.split(' ')[1:]
596
result[key] = (float(values[0]), long(values[1]))
600
def find_branches(self, using=False):
601
"""See Repository.find_branches()."""
602
# should be an API call to the server.
604
return self._real_repository.find_branches(using=using)
606
def get_physical_lock_status(self):
607
"""See Repository.get_physical_lock_status()."""
608
# should be an API call to the server.
610
return self._real_repository.get_physical_lock_status()
612
def is_in_write_group(self):
613
"""Return True if there is an open write group.
615
write groups are only applicable locally for the smart server..
617
if self._real_repository:
618
return self._real_repository.is_in_write_group()
621
return self._lock_count >= 1
624
"""See Repository.is_shared()."""
625
path = self.bzrdir._path_for_remote_call(self._client)
626
response = self._call('Repository.is_shared', path)
627
if response[0] not in ('yes', 'no'):
628
raise SmartProtocolError('unexpected response code %s' % (response,))
629
return response[0] == 'yes'
631
def is_write_locked(self):
632
return self._lock_mode == 'w'
635
# wrong eventually - want a local lock cache context
636
if not self._lock_mode:
637
self._lock_mode = 'r'
639
self._unstacked_provider.enable_cache(cache_misses=False)
640
if self._real_repository is not None:
641
self._real_repository.lock_read()
643
self._lock_count += 1
645
def _remote_lock_write(self, token):
646
path = self.bzrdir._path_for_remote_call(self._client)
649
err_context = {'token': token}
650
response = self._call('Repository.lock_write', path, token,
652
if response[0] == 'ok':
656
raise errors.UnexpectedSmartServerResponse(response)
658
def lock_write(self, token=None, _skip_rpc=False):
659
if not self._lock_mode:
661
if self._lock_token is not None:
662
if token != self._lock_token:
663
raise errors.TokenMismatch(token, self._lock_token)
664
self._lock_token = token
666
self._lock_token = self._remote_lock_write(token)
667
# if self._lock_token is None, then this is something like packs or
668
# svn where we don't get to lock the repo, or a weave style repository
669
# where we cannot lock it over the wire and attempts to do so will
671
if self._real_repository is not None:
672
self._real_repository.lock_write(token=self._lock_token)
673
if token is not None:
674
self._leave_lock = True
676
self._leave_lock = False
677
self._lock_mode = 'w'
679
self._unstacked_provider.enable_cache(cache_misses=False)
680
elif self._lock_mode == 'r':
681
raise errors.ReadOnlyError(self)
683
self._lock_count += 1
684
return self._lock_token or None
686
def leave_lock_in_place(self):
687
if not self._lock_token:
688
raise NotImplementedError(self.leave_lock_in_place)
689
self._leave_lock = True
691
def dont_leave_lock_in_place(self):
692
if not self._lock_token:
693
raise NotImplementedError(self.dont_leave_lock_in_place)
694
self._leave_lock = False
696
def _set_real_repository(self, repository):
697
"""Set the _real_repository for this repository.
699
:param repository: The repository to fallback to for non-hpss
700
implemented operations.
702
if self._real_repository is not None:
703
raise AssertionError('_real_repository is already set')
704
if isinstance(repository, RemoteRepository):
705
raise AssertionError()
706
self._real_repository = repository
707
for fb in self._fallback_repositories:
708
self._real_repository.add_fallback_repository(fb)
709
if self._lock_mode == 'w':
710
# if we are already locked, the real repository must be able to
711
# acquire the lock with our token.
712
self._real_repository.lock_write(self._lock_token)
713
elif self._lock_mode == 'r':
714
self._real_repository.lock_read()
716
def start_write_group(self):
717
"""Start a write group on the decorated repository.
719
Smart methods peform operations in a single step so this api
720
is not really applicable except as a compatibility thunk
721
for older plugins that don't use e.g. the CommitBuilder
725
return self._real_repository.start_write_group()
727
def _unlock(self, token):
728
path = self.bzrdir._path_for_remote_call(self._client)
730
# with no token the remote repository is not persistently locked.
732
err_context = {'token': token}
733
response = self._call('Repository.unlock', path, token,
735
if response == ('ok',):
738
raise errors.UnexpectedSmartServerResponse(response)
741
self._lock_count -= 1
742
if self._lock_count > 0:
744
self._unstacked_provider.disable_cache()
745
old_mode = self._lock_mode
746
self._lock_mode = None
748
# The real repository is responsible at present for raising an
749
# exception if it's in an unfinished write group. However, it
750
# normally will *not* actually remove the lock from disk - that's
751
# done by the server on receiving the Repository.unlock call.
752
# This is just to let the _real_repository stay up to date.
753
if self._real_repository is not None:
754
self._real_repository.unlock()
756
# The rpc-level lock should be released even if there was a
757
# problem releasing the vfs-based lock.
759
# Only write-locked repositories need to make a remote method
760
# call to perfom the unlock.
761
old_token = self._lock_token
762
self._lock_token = None
763
if not self._leave_lock:
764
self._unlock(old_token)
766
def break_lock(self):
767
# should hand off to the network
769
return self._real_repository.break_lock()
771
def _get_tarball(self, compression):
772
"""Return a TemporaryFile containing a repository tarball.
774
Returns None if the server does not support sending tarballs.
777
path = self.bzrdir._path_for_remote_call(self._client)
779
response, protocol = self._call_expecting_body(
780
'Repository.tarball', path, compression)
781
except errors.UnknownSmartMethod:
782
protocol.cancel_read_body()
784
if response[0] == 'ok':
785
# Extract the tarball and return it
786
t = tempfile.NamedTemporaryFile()
787
# TODO: rpc layer should read directly into it...
788
t.write(protocol.read_body_bytes())
791
raise errors.UnexpectedSmartServerResponse(response)
793
def sprout(self, to_bzrdir, revision_id=None):
794
# TODO: Option to control what format is created?
796
dest_repo = self._real_repository._format.initialize(to_bzrdir,
798
dest_repo.fetch(self, revision_id=revision_id)
801
### These methods are just thin shims to the VFS object for now.
803
def revision_tree(self, revision_id):
805
return self._real_repository.revision_tree(revision_id)
807
def get_serializer_format(self):
809
return self._real_repository.get_serializer_format()
811
def get_commit_builder(self, branch, parents, config, timestamp=None,
812
timezone=None, committer=None, revprops=None,
814
# FIXME: It ought to be possible to call this without immediately
815
# triggering _ensure_real. For now it's the easiest thing to do.
817
real_repo = self._real_repository
818
builder = real_repo.get_commit_builder(branch, parents,
819
config, timestamp=timestamp, timezone=timezone,
820
committer=committer, revprops=revprops, revision_id=revision_id)
823
def add_fallback_repository(self, repository):
824
"""Add a repository to use for looking up data not held locally.
826
:param repository: A repository.
828
# XXX: At the moment the RemoteRepository will allow fallbacks
829
# unconditionally - however, a _real_repository will usually exist,
830
# and may raise an error if it's not accommodated by the underlying
831
# format. Eventually we should check when opening the repository
832
# whether it's willing to allow them or not.
834
# We need to accumulate additional repositories here, to pass them in
836
self._fallback_repositories.append(repository)
837
# They are also seen by the fallback repository. If it doesn't exist
838
# yet they'll be added then. This implicitly copies them.
841
def add_inventory(self, revid, inv, parents):
843
return self._real_repository.add_inventory(revid, inv, parents)
845
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
848
return self._real_repository.add_inventory_by_delta(basis_revision_id,
849
delta, new_revision_id, parents)
851
def add_revision(self, rev_id, rev, inv=None, config=None):
853
return self._real_repository.add_revision(
854
rev_id, rev, inv=inv, config=config)
857
def get_inventory(self, revision_id):
859
return self._real_repository.get_inventory(revision_id)
861
def iter_inventories(self, revision_ids):
863
return self._real_repository.iter_inventories(revision_ids)
866
def get_revision(self, revision_id):
868
return self._real_repository.get_revision(revision_id)
870
def get_transaction(self):
872
return self._real_repository.get_transaction()
875
def clone(self, a_bzrdir, revision_id=None):
877
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
879
def make_working_trees(self):
880
"""See Repository.make_working_trees"""
882
return self._real_repository.make_working_trees()
884
def revision_ids_to_search_result(self, result_set):
885
"""Convert a set of revision ids to a graph SearchResult."""
886
result_parents = set()
887
for parents in self.get_graph().get_parent_map(
888
result_set).itervalues():
889
result_parents.update(parents)
890
included_keys = result_set.intersection(result_parents)
891
start_keys = result_set.difference(included_keys)
892
exclude_keys = result_parents.difference(result_set)
893
result = graph.SearchResult(start_keys, exclude_keys,
894
len(result_set), result_set)
898
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
899
"""Return the revision ids that other has that this does not.
901
These are returned in topological order.
903
revision_id: only return revision ids included by revision_id.
905
return repository.InterRepository.get(
906
other, self).search_missing_revision_ids(revision_id, find_ghosts)
908
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
909
# Not delegated to _real_repository so that InterRepository.get has a
910
# chance to find an InterRepository specialised for RemoteRepository.
911
if self.has_same_location(source):
912
# check that last_revision is in 'from' and then return a
914
if (revision_id is not None and
915
not revision.is_null(revision_id)):
916
self.get_revision(revision_id)
918
inter = repository.InterRepository.get(source, self)
920
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
921
except NotImplementedError:
922
raise errors.IncompatibleRepositories(source, self)
924
def create_bundle(self, target, base, fileobj, format=None):
926
self._real_repository.create_bundle(target, base, fileobj, format)
929
def get_ancestry(self, revision_id, topo_sorted=True):
931
return self._real_repository.get_ancestry(revision_id, topo_sorted)
933
def fileids_altered_by_revision_ids(self, revision_ids):
935
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
937
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
939
return self._real_repository._get_versioned_file_checker(
940
revisions, revision_versions_cache)
942
def iter_files_bytes(self, desired_files):
943
"""See Repository.iter_file_bytes.
946
return self._real_repository.iter_files_bytes(desired_files)
949
def _fetch_order(self):
950
"""Decorate the real repository for now.
952
In the long term getting this back from the remote repository as part
953
of open would be more efficient.
956
return self._real_repository._fetch_order
959
def _fetch_uses_deltas(self):
960
"""Decorate the real repository for now.
962
In the long term getting this back from the remote repository as part
963
of open would be more efficient.
966
return self._real_repository._fetch_uses_deltas
969
def _fetch_reconcile(self):
970
"""Decorate the real repository for now.
972
In the long term getting this back from the remote repository as part
973
of open would be more efficient.
976
return self._real_repository._fetch_reconcile
978
def get_parent_map(self, revision_ids):
979
"""See bzrlib.Graph.get_parent_map()."""
980
return self._make_parents_provider().get_parent_map(revision_ids)
982
def _get_parent_map_rpc(self, keys):
983
"""Helper for get_parent_map that performs the RPC."""
984
medium = self._client._medium
985
if medium._is_remote_before((1, 2)):
986
# We already found out that the server can't understand
987
# Repository.get_parent_map requests, so just fetch the whole
989
# XXX: Note that this will issue a deprecation warning. This is ok
990
# :- its because we're working with a deprecated server anyway, and
991
# the user will almost certainly have seen a warning about the
992
# server version already.
993
rg = self.get_revision_graph()
994
# There is an api discrepency between get_parent_map and
995
# get_revision_graph. Specifically, a "key:()" pair in
996
# get_revision_graph just means a node has no parents. For
997
# "get_parent_map" it means the node is a ghost. So fix up the
998
# graph to correct this.
999
# https://bugs.launchpad.net/bzr/+bug/214894
1000
# There is one other "bug" which is that ghosts in
1001
# get_revision_graph() are not returned at all. But we won't worry
1002
# about that for now.
1003
for node_id, parent_ids in rg.iteritems():
1004
if parent_ids == ():
1005
rg[node_id] = (NULL_REVISION,)
1006
rg[NULL_REVISION] = ()
1011
raise ValueError('get_parent_map(None) is not valid')
1012
if NULL_REVISION in keys:
1013
keys.discard(NULL_REVISION)
1014
found_parents = {NULL_REVISION:()}
1016
return found_parents
1019
# TODO(Needs analysis): We could assume that the keys being requested
1020
# from get_parent_map are in a breadth first search, so typically they
1021
# will all be depth N from some common parent, and we don't have to
1022
# have the server iterate from the root parent, but rather from the
1023
# keys we're searching; and just tell the server the keyspace we
1024
# already have; but this may be more traffic again.
1026
# Transform self._parents_map into a search request recipe.
1027
# TODO: Manage this incrementally to avoid covering the same path
1028
# repeatedly. (The server will have to on each request, but the less
1029
# work done the better).
1030
parents_map = self._unstacked_provider.get_cached_map()
1031
if parents_map is None:
1032
# Repository is not locked, so there's no cache.
1034
start_set = set(parents_map)
1035
result_parents = set()
1036
for parents in parents_map.itervalues():
1037
result_parents.update(parents)
1038
stop_keys = result_parents.difference(start_set)
1039
included_keys = start_set.intersection(result_parents)
1040
start_set.difference_update(included_keys)
1041
recipe = (start_set, stop_keys, len(parents_map))
1042
body = self._serialise_search_recipe(recipe)
1043
path = self.bzrdir._path_for_remote_call(self._client)
1045
if type(key) is not str:
1047
"key %r not a plain string" % (key,))
1048
verb = 'Repository.get_parent_map'
1049
args = (path,) + tuple(keys)
1051
response = self._call_with_body_bytes_expecting_body(
1053
except errors.UnknownSmartMethod:
1054
# Server does not support this method, so get the whole graph.
1055
# Worse, we have to force a disconnection, because the server now
1056
# doesn't realise it has a body on the wire to consume, so the
1057
# only way to recover is to abandon the connection.
1059
'Server is too old for fast get_parent_map, reconnecting. '
1060
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1062
# To avoid having to disconnect repeatedly, we keep track of the
1063
# fact the server doesn't understand remote methods added in 1.2.
1064
medium._remember_remote_is_before((1, 2))
1065
return self.get_revision_graph(None)
1066
response_tuple, response_handler = response
1067
if response_tuple[0] not in ['ok']:
1068
response_handler.cancel_read_body()
1069
raise errors.UnexpectedSmartServerResponse(response_tuple)
1070
if response_tuple[0] == 'ok':
1071
coded = bz2.decompress(response_handler.read_body_bytes())
1073
# no revisions found
1075
lines = coded.split('\n')
1078
d = tuple(line.split())
1080
revision_graph[d[0]] = d[1:]
1082
# No parents - so give the Graph result (NULL_REVISION,).
1083
revision_graph[d[0]] = (NULL_REVISION,)
1084
return revision_graph
1087
def get_signature_text(self, revision_id):
1089
return self._real_repository.get_signature_text(revision_id)
1092
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1093
def get_revision_graph_with_ghosts(self, revision_ids=None):
1095
return self._real_repository.get_revision_graph_with_ghosts(
1096
revision_ids=revision_ids)
1099
def get_inventory_xml(self, revision_id):
1101
return self._real_repository.get_inventory_xml(revision_id)
1103
def deserialise_inventory(self, revision_id, xml):
1105
return self._real_repository.deserialise_inventory(revision_id, xml)
1107
def reconcile(self, other=None, thorough=False):
1109
return self._real_repository.reconcile(other=other, thorough=thorough)
1111
def all_revision_ids(self):
1113
return self._real_repository.all_revision_ids()
1116
def get_deltas_for_revisions(self, revisions):
1118
return self._real_repository.get_deltas_for_revisions(revisions)
1121
def get_revision_delta(self, revision_id):
1123
return self._real_repository.get_revision_delta(revision_id)
1126
def revision_trees(self, revision_ids):
1128
return self._real_repository.revision_trees(revision_ids)
1131
def get_revision_reconcile(self, revision_id):
1133
return self._real_repository.get_revision_reconcile(revision_id)
1136
def check(self, revision_ids=None):
1138
return self._real_repository.check(revision_ids=revision_ids)
1140
def copy_content_into(self, destination, revision_id=None):
1142
return self._real_repository.copy_content_into(
1143
destination, revision_id=revision_id)
1145
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1146
# get a tarball of the remote repository, and copy from that into the
1148
from bzrlib import osutils
1150
# TODO: Maybe a progress bar while streaming the tarball?
1151
note("Copying repository content as tarball...")
1152
tar_file = self._get_tarball('bz2')
1153
if tar_file is None:
1155
destination = to_bzrdir.create_repository()
1157
tar = tarfile.open('repository', fileobj=tar_file,
1159
tmpdir = osutils.mkdtemp()
1161
_extract_tar(tar, tmpdir)
1162
tmp_bzrdir = BzrDir.open(tmpdir)
1163
tmp_repo = tmp_bzrdir.open_repository()
1164
tmp_repo.copy_content_into(destination, revision_id)
1166
osutils.rmtree(tmpdir)
1170
# TODO: Suggestion from john: using external tar is much faster than
1171
# python's tarfile library, but it may not work on windows.
1174
def inventories(self):
1175
"""Decorate the real repository for now.
1177
In the long term a full blown network facility is needed to
1178
avoid creating a real repository object locally.
1181
return self._real_repository.inventories
1185
"""Compress the data within the repository.
1187
This is not currently implemented within the smart server.
1190
return self._real_repository.pack()
1193
def revisions(self):
1194
"""Decorate the real repository for now.
1196
In the short term this should become a real object to intercept graph
1199
In the long term a full blown network facility is needed.
1202
return self._real_repository.revisions
1204
def set_make_working_trees(self, new_value):
1206
new_value_str = "True"
1208
new_value_str = "False"
1209
path = self.bzrdir._path_for_remote_call(self._client)
1211
response = self._call(
1212
'Repository.set_make_working_trees', path, new_value_str)
1213
except errors.UnknownSmartMethod:
1215
self._real_repository.set_make_working_trees(new_value)
1217
if response[0] != 'ok':
1218
raise errors.UnexpectedSmartServerResponse(response)
1221
def signatures(self):
1222
"""Decorate the real repository for now.
1224
In the long term a full blown network facility is needed to avoid
1225
creating a real repository object locally.
1228
return self._real_repository.signatures
1231
def sign_revision(self, revision_id, gpg_strategy):
1233
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1237
"""Decorate the real repository for now.
1239
In the long term a full blown network facility is needed to avoid
1240
creating a real repository object locally.
1243
return self._real_repository.texts
1246
def get_revisions(self, revision_ids):
1248
return self._real_repository.get_revisions(revision_ids)
1250
def supports_rich_root(self):
1252
return self._real_repository.supports_rich_root()
1254
def iter_reverse_revision_history(self, revision_id):
1256
return self._real_repository.iter_reverse_revision_history(revision_id)
1259
def _serializer(self):
1261
return self._real_repository._serializer
1263
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1265
return self._real_repository.store_revision_signature(
1266
gpg_strategy, plaintext, revision_id)
1268
def add_signature_text(self, revision_id, signature):
1270
return self._real_repository.add_signature_text(revision_id, signature)
1272
def has_signature_for_revision_id(self, revision_id):
1274
return self._real_repository.has_signature_for_revision_id(revision_id)
1276
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1278
return self._real_repository.item_keys_introduced_by(revision_ids,
1279
_files_pb=_files_pb)
1281
def revision_graph_can_have_wrong_parents(self):
1282
# The answer depends on the remote repo format.
1284
return self._real_repository.revision_graph_can_have_wrong_parents()
1286
def _find_inconsistent_revision_parents(self):
1288
return self._real_repository._find_inconsistent_revision_parents()
1290
def _check_for_inconsistent_revision_parents(self):
1292
return self._real_repository._check_for_inconsistent_revision_parents()
1294
def _make_parents_provider(self, other=None):
1295
providers = [self._unstacked_provider]
1296
if other is not None:
1297
providers.insert(0, other)
1298
providers.extend(r._make_parents_provider() for r in
1299
self._fallback_repositories)
1300
return graph._StackedParentsProvider(providers)
1302
def _serialise_search_recipe(self, recipe):
1303
"""Serialise a graph search recipe.
1305
:param recipe: A search recipe (start, stop, count).
1306
:return: Serialised bytes.
1308
start_keys = ' '.join(recipe[0])
1309
stop_keys = ' '.join(recipe[1])
1310
count = str(recipe[2])
1311
return '\n'.join((start_keys, stop_keys, count))
1314
path = self.bzrdir._path_for_remote_call(self._client)
1316
response = self._call('PackRepository.autopack', path)
1317
except errors.UnknownSmartMethod:
1319
self._real_repository._pack_collection.autopack()
1321
if self._real_repository is not None:
1322
# Reset the real repository's cache of pack names.
1323
# XXX: At some point we may be able to skip this and just rely on
1324
# the automatic retry logic to do the right thing, but for now we
1325
# err on the side of being correct rather than being optimal.
1326
self._real_repository._pack_collection.reload_pack_names()
1327
if response[0] != 'ok':
1328
raise errors.UnexpectedSmartServerResponse(response)
1331
class RemoteBranchLockableFiles(LockableFiles):
1332
"""A 'LockableFiles' implementation that talks to a smart server.
1334
This is not a public interface class.
1337
def __init__(self, bzrdir, _client):
1338
self.bzrdir = bzrdir
1339
self._client = _client
1340
self._need_find_modes = True
1341
LockableFiles.__init__(
1342
self, bzrdir.get_branch_transport(None),
1343
'lock', lockdir.LockDir)
1345
def _find_modes(self):
1346
# RemoteBranches don't let the client set the mode of control files.
1347
self._dir_mode = None
1348
self._file_mode = None
1351
class RemoteBranchFormat(branch.BranchFormat):
1354
super(RemoteBranchFormat, self).__init__()
1355
self._matchingbzrdir = RemoteBzrDirFormat()
1356
self._matchingbzrdir.set_branch_format(self)
1358
def __eq__(self, other):
1359
return (isinstance(other, RemoteBranchFormat) and
1360
self.__dict__ == other.__dict__)
1362
def get_format_description(self):
1363
return 'Remote BZR Branch'
1365
def get_format_string(self):
1366
return 'Remote BZR Branch'
1368
def open(self, a_bzrdir):
1369
return a_bzrdir.open_branch()
1371
def initialize(self, a_bzrdir):
1372
# Delegate to a _real object here - the RemoteBzrDir format now
1373
# supports delegating to parameterised branch formats and as such
1374
# this RemoteBranchFormat method is only called when no specific format
1376
if not isinstance(a_bzrdir, RemoteBzrDir):
1377
result = a_bzrdir.create_branch()
1379
a_bzrdir._ensure_real()
1380
result = a_bzrdir._real_bzrdir.create_branch()
1381
if not isinstance(result, RemoteBranch):
1382
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1385
def supports_tags(self):
1386
# Remote branches might support tags, but we won't know until we
1387
# access the real remote branch.
1391
class RemoteBranch(branch.Branch, _RpcHelper):
1392
"""Branch stored on a server accessed by HPSS RPC.
1394
At the moment most operations are mapped down to simple file operations.
1397
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1399
"""Create a RemoteBranch instance.
1401
:param real_branch: An optional local implementation of the branch
1402
format, usually accessing the data via the VFS.
1403
:param _client: Private parameter for testing.
1405
# We intentionally don't call the parent class's __init__, because it
1406
# will try to assign to self.tags, which is a property in this subclass.
1407
# And the parent's __init__ doesn't do much anyway.
1408
self._revision_id_to_revno_cache = None
1409
self._partial_revision_id_to_revno_cache = {}
1410
self._revision_history_cache = None
1411
self._last_revision_info_cache = None
1412
self._merge_sorted_revisions_cache = None
1413
self.bzrdir = remote_bzrdir
1414
if _client is not None:
1415
self._client = _client
1417
self._client = remote_bzrdir._client
1418
self.repository = remote_repository
1419
if real_branch is not None:
1420
self._real_branch = real_branch
1421
# Give the remote repository the matching real repo.
1422
real_repo = self._real_branch.repository
1423
if isinstance(real_repo, RemoteRepository):
1424
real_repo._ensure_real()
1425
real_repo = real_repo._real_repository
1426
self.repository._set_real_repository(real_repo)
1427
# Give the branch the remote repository to let fast-pathing happen.
1428
self._real_branch.repository = self.repository
1430
self._real_branch = None
1431
# Fill out expected attributes of branch for bzrlib api users.
1432
self._format = RemoteBranchFormat()
1433
self.base = self.bzrdir.root_transport.base
1434
self._control_files = None
1435
self._lock_mode = None
1436
self._lock_token = None
1437
self._repo_lock_token = None
1438
self._lock_count = 0
1439
self._leave_lock = False
1440
# The base class init is not called, so we duplicate this:
1441
hooks = branch.Branch.hooks['open']
1444
self._setup_stacking()
1446
def _setup_stacking(self):
1447
# configure stacking into the remote repository, by reading it from
1450
fallback_url = self.get_stacked_on_url()
1451
except (errors.NotStacked, errors.UnstackableBranchFormat,
1452
errors.UnstackableRepositoryFormat), e:
1454
# it's relative to this branch...
1455
fallback_url = urlutils.join(self.base, fallback_url)
1456
transports = [self.bzrdir.root_transport]
1457
if self._real_branch is not None:
1458
transports.append(self._real_branch._transport)
1459
stacked_on = branch.Branch.open(fallback_url,
1460
possible_transports=transports)
1461
self.repository.add_fallback_repository(stacked_on.repository)
1463
def _get_real_transport(self):
1464
# if we try vfs access, return the real branch's vfs transport
1466
return self._real_branch._transport
1468
_transport = property(_get_real_transport)
1471
return "%s(%s)" % (self.__class__.__name__, self.base)
1475
def _ensure_real(self):
1476
"""Ensure that there is a _real_branch set.
1478
Used before calls to self._real_branch.
1480
if self._real_branch is None:
1481
if not vfs.vfs_enabled():
1482
raise AssertionError('smart server vfs must be enabled '
1483
'to use vfs implementation')
1484
self.bzrdir._ensure_real()
1485
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1486
if self.repository._real_repository is None:
1487
# Give the remote repository the matching real repo.
1488
real_repo = self._real_branch.repository
1489
if isinstance(real_repo, RemoteRepository):
1490
real_repo._ensure_real()
1491
real_repo = real_repo._real_repository
1492
self.repository._set_real_repository(real_repo)
1493
# Give the real branch the remote repository to let fast-pathing
1495
self._real_branch.repository = self.repository
1496
if self._lock_mode == 'r':
1497
self._real_branch.lock_read()
1498
elif self._lock_mode == 'w':
1499
self._real_branch.lock_write(token=self._lock_token)
1501
def _translate_error(self, err, **context):
1502
self.repository._translate_error(err, branch=self, **context)
1504
def _clear_cached_state(self):
1505
super(RemoteBranch, self)._clear_cached_state()
1506
if self._real_branch is not None:
1507
self._real_branch._clear_cached_state()
1509
def _clear_cached_state_of_remote_branch_only(self):
1510
"""Like _clear_cached_state, but doesn't clear the cache of
1513
This is useful when falling back to calling a method of
1514
self._real_branch that changes state. In that case the underlying
1515
branch changes, so we need to invalidate this RemoteBranch's cache of
1516
it. However, there's no need to invalidate the _real_branch's cache
1517
too, in fact doing so might harm performance.
1519
super(RemoteBranch, self)._clear_cached_state()
1522
def control_files(self):
1523
# Defer actually creating RemoteBranchLockableFiles until its needed,
1524
# because it triggers an _ensure_real that we otherwise might not need.
1525
if self._control_files is None:
1526
self._control_files = RemoteBranchLockableFiles(
1527
self.bzrdir, self._client)
1528
return self._control_files
1530
def _get_checkout_format(self):
1532
return self._real_branch._get_checkout_format()
1534
def get_physical_lock_status(self):
1535
"""See Branch.get_physical_lock_status()."""
1536
# should be an API call to the server, as branches must be lockable.
1538
return self._real_branch.get_physical_lock_status()
1540
def get_stacked_on_url(self):
1541
"""Get the URL this branch is stacked against.
1543
:raises NotStacked: If the branch is not stacked.
1544
:raises UnstackableBranchFormat: If the branch does not support
1546
:raises UnstackableRepositoryFormat: If the repository does not support
1550
# there may not be a repository yet, so we can't use
1551
# self._translate_error, so we can't use self._call either.
1552
response = self._client.call('Branch.get_stacked_on_url',
1553
self._remote_path())
1554
except errors.ErrorFromSmartServer, err:
1555
# there may not be a repository yet, so we can't call through
1556
# its _translate_error
1557
_translate_error(err, branch=self)
1558
except errors.UnknownSmartMethod, err:
1560
return self._real_branch.get_stacked_on_url()
1561
if response[0] != 'ok':
1562
raise errors.UnexpectedSmartServerResponse(response)
1565
def lock_read(self):
1566
self.repository.lock_read()
1567
if not self._lock_mode:
1568
self._lock_mode = 'r'
1569
self._lock_count = 1
1570
if self._real_branch is not None:
1571
self._real_branch.lock_read()
1573
self._lock_count += 1
1575
def _remote_lock_write(self, token):
1577
branch_token = repo_token = ''
1579
branch_token = token
1580
repo_token = self.repository.lock_write()
1581
self.repository.unlock()
1582
err_context = {'token': token}
1583
response = self._call(
1584
'Branch.lock_write', self._remote_path(), branch_token,
1585
repo_token or '', **err_context)
1586
if response[0] != 'ok':
1587
raise errors.UnexpectedSmartServerResponse(response)
1588
ok, branch_token, repo_token = response
1589
return branch_token, repo_token
1591
def lock_write(self, token=None):
1592
if not self._lock_mode:
1593
# Lock the branch and repo in one remote call.
1594
remote_tokens = self._remote_lock_write(token)
1595
self._lock_token, self._repo_lock_token = remote_tokens
1596
if not self._lock_token:
1597
raise SmartProtocolError('Remote server did not return a token!')
1598
# Tell the self.repository object that it is locked.
1599
self.repository.lock_write(
1600
self._repo_lock_token, _skip_rpc=True)
1602
if self._real_branch is not None:
1603
self._real_branch.lock_write(token=self._lock_token)
1604
if token is not None:
1605
self._leave_lock = True
1607
self._leave_lock = False
1608
self._lock_mode = 'w'
1609
self._lock_count = 1
1610
elif self._lock_mode == 'r':
1611
raise errors.ReadOnlyTransaction
1613
if token is not None:
1614
# A token was given to lock_write, and we're relocking, so
1615
# check that the given token actually matches the one we
1617
if token != self._lock_token:
1618
raise errors.TokenMismatch(token, self._lock_token)
1619
self._lock_count += 1
1620
# Re-lock the repository too.
1621
self.repository.lock_write(self._repo_lock_token)
1622
return self._lock_token or None
1624
def _unlock(self, branch_token, repo_token):
1625
err_context = {'token': str((branch_token, repo_token))}
1626
response = self._call(
1627
'Branch.unlock', self._remote_path(), branch_token,
1628
repo_token or '', **err_context)
1629
if response == ('ok',):
1631
raise errors.UnexpectedSmartServerResponse(response)
1635
self._lock_count -= 1
1636
if not self._lock_count:
1637
self._clear_cached_state()
1638
mode = self._lock_mode
1639
self._lock_mode = None
1640
if self._real_branch is not None:
1641
if (not self._leave_lock and mode == 'w' and
1642
self._repo_lock_token):
1643
# If this RemoteBranch will remove the physical lock
1644
# for the repository, make sure the _real_branch
1645
# doesn't do it first. (Because the _real_branch's
1646
# repository is set to be the RemoteRepository.)
1647
self._real_branch.repository.leave_lock_in_place()
1648
self._real_branch.unlock()
1650
# Only write-locked branched need to make a remote method
1651
# call to perfom the unlock.
1653
if not self._lock_token:
1654
raise AssertionError('Locked, but no token!')
1655
branch_token = self._lock_token
1656
repo_token = self._repo_lock_token
1657
self._lock_token = None
1658
self._repo_lock_token = None
1659
if not self._leave_lock:
1660
self._unlock(branch_token, repo_token)
1662
self.repository.unlock()
1664
def break_lock(self):
1666
return self._real_branch.break_lock()
1668
def leave_lock_in_place(self):
1669
if not self._lock_token:
1670
raise NotImplementedError(self.leave_lock_in_place)
1671
self._leave_lock = True
1673
def dont_leave_lock_in_place(self):
1674
if not self._lock_token:
1675
raise NotImplementedError(self.dont_leave_lock_in_place)
1676
self._leave_lock = False
1678
def _last_revision_info(self):
1679
response = self._call('Branch.last_revision_info', self._remote_path())
1680
if response[0] != 'ok':
1681
raise SmartProtocolError('unexpected response code %s' % (response,))
1682
revno = int(response[1])
1683
last_revision = response[2]
1684
return (revno, last_revision)
1686
def _gen_revision_history(self):
1687
"""See Branch._gen_revision_history()."""
1688
response_tuple, response_handler = self._call_expecting_body(
1689
'Branch.revision_history', self._remote_path())
1690
if response_tuple[0] != 'ok':
1691
raise errors.UnexpectedSmartServerResponse(response_tuple)
1692
result = response_handler.read_body_bytes().split('\x00')
1697
def _remote_path(self):
1698
return self.bzrdir._path_for_remote_call(self._client)
1700
def _set_last_revision_descendant(self, revision_id, other_branch,
1701
allow_diverged=False, allow_overwrite_descendant=False):
1702
# This performs additional work to meet the hook contract; while its
1703
# undesirable, we have to synthesise the revno to call the hook, and
1704
# not calling the hook is worse as it means changes can't be prevented.
1705
# Having calculated this though, we can't just call into
1706
# set_last_revision_info as a simple call, because there is a set_rh
1707
# hook that some folk may still be using.
1708
old_revno, old_revid = self.last_revision_info()
1709
history = self._lefthand_history(revision_id)
1710
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1711
err_context = {'other_branch': other_branch}
1712
response = self._call('Branch.set_last_revision_ex',
1713
self._remote_path(), self._lock_token, self._repo_lock_token,
1714
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1716
self._clear_cached_state()
1717
if len(response) != 3 and response[0] != 'ok':
1718
raise errors.UnexpectedSmartServerResponse(response)
1719
new_revno, new_revision_id = response[1:]
1720
self._last_revision_info_cache = new_revno, new_revision_id
1721
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1722
if self._real_branch is not None:
1723
cache = new_revno, new_revision_id
1724
self._real_branch._last_revision_info_cache = cache
1726
def _set_last_revision(self, revision_id):
1727
old_revno, old_revid = self.last_revision_info()
1728
# This performs additional work to meet the hook contract; while its
1729
# undesirable, we have to synthesise the revno to call the hook, and
1730
# not calling the hook is worse as it means changes can't be prevented.
1731
# Having calculated this though, we can't just call into
1732
# set_last_revision_info as a simple call, because there is a set_rh
1733
# hook that some folk may still be using.
1734
history = self._lefthand_history(revision_id)
1735
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1736
self._clear_cached_state()
1737
response = self._call('Branch.set_last_revision',
1738
self._remote_path(), self._lock_token, self._repo_lock_token,
1740
if response != ('ok',):
1741
raise errors.UnexpectedSmartServerResponse(response)
1742
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1745
def set_revision_history(self, rev_history):
1746
# Send just the tip revision of the history; the server will generate
1747
# the full history from that. If the revision doesn't exist in this
1748
# branch, NoSuchRevision will be raised.
1749
if rev_history == []:
1752
rev_id = rev_history[-1]
1753
self._set_last_revision(rev_id)
1754
for hook in branch.Branch.hooks['set_rh']:
1755
hook(self, rev_history)
1756
self._cache_revision_history(rev_history)
1758
def get_parent(self):
1760
return self._real_branch.get_parent()
1762
def _get_parent_location(self):
1763
# Used by tests, when checking normalisation of given vs stored paths.
1765
return self._real_branch._get_parent_location()
1767
def set_parent(self, url):
1769
return self._real_branch.set_parent(url)
1771
def _set_parent_location(self, url):
1772
# Used by tests, to poke bad urls into branch configurations
1774
self.set_parent(url)
1777
return self._real_branch._set_parent_location(url)
1779
def set_stacked_on_url(self, stacked_location):
1780
"""Set the URL this branch is stacked against.
1782
:raises UnstackableBranchFormat: If the branch does not support
1784
:raises UnstackableRepositoryFormat: If the repository does not support
1788
return self._real_branch.set_stacked_on_url(stacked_location)
1790
def sprout(self, to_bzrdir, revision_id=None):
1791
branch_format = to_bzrdir._format._branch_format
1792
if (branch_format is None or
1793
isinstance(branch_format, RemoteBranchFormat)):
1794
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1795
# implies the same thing), but RemoteBranches can't be created at
1796
# arbitrary URLs. So create a branch in the same format as
1797
# _real_branch instead.
1798
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1799
# to_bzrdir.create_branch to create a RemoteBranch after all...
1801
result = self._real_branch._format.initialize(to_bzrdir)
1802
self.copy_content_into(result, revision_id=revision_id)
1803
result.set_parent(self.bzrdir.root_transport.base)
1805
result = branch.Branch.sprout(
1806
self, to_bzrdir, revision_id=revision_id)
1810
def pull(self, source, overwrite=False, stop_revision=None,
1812
self._clear_cached_state_of_remote_branch_only()
1814
return self._real_branch.pull(
1815
source, overwrite=overwrite, stop_revision=stop_revision,
1816
_override_hook_target=self, **kwargs)
1819
def push(self, target, overwrite=False, stop_revision=None):
1821
return self._real_branch.push(
1822
target, overwrite=overwrite, stop_revision=stop_revision,
1823
_override_hook_source_branch=self)
1825
def is_locked(self):
1826
return self._lock_count >= 1
1829
def revision_id_to_revno(self, revision_id):
1831
return self._real_branch.revision_id_to_revno(revision_id)
1834
def set_last_revision_info(self, revno, revision_id):
1835
# XXX: These should be returned by the set_last_revision_info verb
1836
old_revno, old_revid = self.last_revision_info()
1837
self._run_pre_change_branch_tip_hooks(revno, revision_id)
1838
revision_id = ensure_null(revision_id)
1840
response = self._call('Branch.set_last_revision_info',
1841
self._remote_path(), self._lock_token, self._repo_lock_token,
1842
str(revno), revision_id)
1843
except errors.UnknownSmartMethod:
1845
self._clear_cached_state_of_remote_branch_only()
1846
self._real_branch.set_last_revision_info(revno, revision_id)
1847
self._last_revision_info_cache = revno, revision_id
1849
if response == ('ok',):
1850
self._clear_cached_state()
1851
self._last_revision_info_cache = revno, revision_id
1852
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1853
# Update the _real_branch's cache too.
1854
if self._real_branch is not None:
1855
cache = self._last_revision_info_cache
1856
self._real_branch._last_revision_info_cache = cache
1858
raise errors.UnexpectedSmartServerResponse(response)
1861
def generate_revision_history(self, revision_id, last_rev=None,
1863
medium = self._client._medium
1864
if not medium._is_remote_before((1, 6)):
1865
# Use a smart method for 1.6 and above servers
1867
self._set_last_revision_descendant(revision_id, other_branch,
1868
allow_diverged=True, allow_overwrite_descendant=True)
1870
except errors.UnknownSmartMethod:
1871
medium._remember_remote_is_before((1, 6))
1872
self._clear_cached_state_of_remote_branch_only()
1873
self.set_revision_history(self._lefthand_history(revision_id,
1874
last_rev=last_rev,other_branch=other_branch))
1879
return self._real_branch.tags
1881
def set_push_location(self, location):
1883
return self._real_branch.set_push_location(location)
1886
def update_revisions(self, other, stop_revision=None, overwrite=False,
1888
"""See Branch.update_revisions."""
1891
if stop_revision is None:
1892
stop_revision = other.last_revision()
1893
if revision.is_null(stop_revision):
1894
# if there are no commits, we're done.
1896
self.fetch(other, stop_revision)
1899
# Just unconditionally set the new revision. We don't care if
1900
# the branches have diverged.
1901
self._set_last_revision(stop_revision)
1903
medium = self._client._medium
1904
if not medium._is_remote_before((1, 6)):
1906
self._set_last_revision_descendant(stop_revision, other)
1908
except errors.UnknownSmartMethod:
1909
medium._remember_remote_is_before((1, 6))
1910
# Fallback for pre-1.6 servers: check for divergence
1911
# client-side, then do _set_last_revision.
1912
last_rev = revision.ensure_null(self.last_revision())
1914
graph = self.repository.get_graph()
1915
if self._check_if_descendant_or_diverged(
1916
stop_revision, last_rev, graph, other):
1917
# stop_revision is a descendant of last_rev, but we aren't
1918
# overwriting, so we're done.
1920
self._set_last_revision(stop_revision)
1925
def _extract_tar(tar, to_dir):
1926
"""Extract all the contents of a tarfile object.
1928
A replacement for extractall, which is not present in python2.4
1931
tar.extract(tarinfo, to_dir)
1934
def _translate_error(err, **context):
1935
"""Translate an ErrorFromSmartServer into a more useful error.
1937
Possible context keys:
1945
If the error from the server doesn't match a known pattern, then
1946
UnknownErrorFromSmartServer is raised.
1950
return context[name]
1951
except KeyError, key_err:
1952
mutter('Missing key %r in context %r', key_err.args[0], context)
1955
"""Get the path from the context if present, otherwise use first error
1959
return context['path']
1960
except KeyError, key_err:
1962
return err.error_args[0]
1963
except IndexError, idx_err:
1965
'Missing key %r in context %r', key_err.args[0], context)
1968
if err.error_verb == 'NoSuchRevision':
1969
raise NoSuchRevision(find('branch'), err.error_args[0])
1970
elif err.error_verb == 'nosuchrevision':
1971
raise NoSuchRevision(find('repository'), err.error_args[0])
1972
elif err.error_tuple == ('nobranch',):
1973
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1974
elif err.error_verb == 'norepository':
1975
raise errors.NoRepositoryPresent(find('bzrdir'))
1976
elif err.error_verb == 'LockContention':
1977
raise errors.LockContention('(remote lock)')
1978
elif err.error_verb == 'UnlockableTransport':
1979
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1980
elif err.error_verb == 'LockFailed':
1981
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1982
elif err.error_verb == 'TokenMismatch':
1983
raise errors.TokenMismatch(find('token'), '(remote token)')
1984
elif err.error_verb == 'Diverged':
1985
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1986
elif err.error_verb == 'TipChangeRejected':
1987
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1988
elif err.error_verb == 'UnstackableBranchFormat':
1989
raise errors.UnstackableBranchFormat(*err.error_args)
1990
elif err.error_verb == 'UnstackableRepositoryFormat':
1991
raise errors.UnstackableRepositoryFormat(*err.error_args)
1992
elif err.error_verb == 'NotStacked':
1993
raise errors.NotStacked(branch=find('branch'))
1994
elif err.error_verb == 'PermissionDenied':
1996
if len(err.error_args) >= 2:
1997
extra = err.error_args[1]
2000
raise errors.PermissionDenied(path, extra=extra)
2001
elif err.error_verb == 'ReadError':
2003
raise errors.ReadError(path)
2004
elif err.error_verb == 'NoSuchFile':
2006
raise errors.NoSuchFile(path)
2007
elif err.error_verb == 'FileExists':
2008
raise errors.FileExists(err.error_args[0])
2009
elif err.error_verb == 'DirectoryNotEmpty':
2010
raise errors.DirectoryNotEmpty(err.error_args[0])
2011
elif err.error_verb == 'ShortReadvError':
2012
args = err.error_args
2013
raise errors.ShortReadvError(
2014
args[0], int(args[1]), int(args[2]), int(args[3]))
2015
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2016
encoding = str(err.error_args[0]) # encoding must always be a string
2017
val = err.error_args[1]
2018
start = int(err.error_args[2])
2019
end = int(err.error_args[3])
2020
reason = str(err.error_args[4]) # reason must always be a string
2021
if val.startswith('u:'):
2022
val = val[2:].decode('utf-8')
2023
elif val.startswith('s:'):
2024
val = val[2:].decode('base64')
2025
if err.error_verb == 'UnicodeDecodeError':
2026
raise UnicodeDecodeError(encoding, val, start, end, reason)
2027
elif err.error_verb == 'UnicodeEncodeError':
2028
raise UnicodeEncodeError(encoding, val, start, end, reason)
2029
elif err.error_verb == 'ReadOnlyError':
2030
raise errors.TransportNotPossible('readonly transport')
2031
raise errors.UnknownErrorFromSmartServer(err)