1
# Copyright (C) 2006, 2007, 2008, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
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(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
87
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
90
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
93
"""Construct a RemoteBzrDir.
95
:param _client: Private parameter for testing. Disables probing and the
98
BzrDir.__init__(self, transport, format)
99
# this object holds a delegated bzrdir that uses file-level operations
100
# to talk to the other side
101
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
111
self._client = _client
117
def _probe_bzrdir(self):
118
medium = self._client._medium
119
path = self._path_for_remote_call(self._client)
120
if medium._is_remote_before((2, 1)):
124
self._rpc_open_2_1(path)
126
except errors.UnknownSmartMethod:
127
medium._remember_remote_is_before((2, 1))
130
def _rpc_open_2_1(self, path):
131
response = self._call('BzrDir.open_2.1', path)
132
if response == ('no',):
133
raise errors.NotBranchError(path=self.root_transport.base)
134
elif response[0] == 'yes':
135
if response[1] == 'yes':
136
self._has_working_tree = True
137
elif response[1] == 'no':
138
self._has_working_tree = False
140
raise errors.UnexpectedSmartServerResponse(response)
142
raise errors.UnexpectedSmartServerResponse(response)
144
def _rpc_open(self, path):
145
response = self._call('BzrDir.open', path)
146
if response not in [('yes',), ('no',)]:
147
raise errors.UnexpectedSmartServerResponse(response)
148
if response == ('no',):
149
raise errors.NotBranchError(path=self.root_transport.base)
151
def _ensure_real(self):
152
"""Ensure that there is a _real_bzrdir set.
154
Used before calls to self._real_bzrdir.
156
if not self._real_bzrdir:
157
self._real_bzrdir = BzrDir.open_from_transport(
158
self.root_transport, _server_formats=False)
159
self._format._network_name = \
160
self._real_bzrdir._format.network_name()
162
def _translate_error(self, err, **context):
163
_translate_error(err, bzrdir=self, **context)
165
def break_lock(self):
166
# Prevent aliasing problems in the next_open_branch_result cache.
167
# See create_branch for rationale.
168
self._next_open_branch_result = None
169
return BzrDir.break_lock(self)
171
def _vfs_cloning_metadir(self, require_stacking=False):
173
return self._real_bzrdir.cloning_metadir(
174
require_stacking=require_stacking)
176
def cloning_metadir(self, require_stacking=False):
177
medium = self._client._medium
178
if medium._is_remote_before((1, 13)):
179
return self._vfs_cloning_metadir(require_stacking=require_stacking)
180
verb = 'BzrDir.cloning_metadir'
185
path = self._path_for_remote_call(self._client)
187
response = self._call(verb, path, stacking)
188
except errors.UnknownSmartMethod:
189
medium._remember_remote_is_before((1, 13))
190
return self._vfs_cloning_metadir(require_stacking=require_stacking)
191
except errors.UnknownErrorFromSmartServer, err:
192
if err.error_tuple != ('BranchReference',):
194
# We need to resolve the branch reference to determine the
195
# cloning_metadir. This causes unnecessary RPCs to open the
196
# referenced branch (and bzrdir, etc) but only when the caller
197
# didn't already resolve the branch reference.
198
referenced_branch = self.open_branch()
199
return referenced_branch.bzrdir.cloning_metadir()
200
if len(response) != 3:
201
raise errors.UnexpectedSmartServerResponse(response)
202
control_name, repo_name, branch_info = response
203
if len(branch_info) != 2:
204
raise errors.UnexpectedSmartServerResponse(response)
205
branch_ref, branch_name = branch_info
206
format = bzrdir.network_format_registry.get(control_name)
208
format.repository_format = repository.network_format_registry.get(
210
if branch_ref == 'ref':
211
# XXX: we need possible_transports here to avoid reopening the
212
# connection to the referenced location
213
ref_bzrdir = BzrDir.open(branch_name)
214
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
215
format.set_branch_format(branch_format)
216
elif branch_ref == 'branch':
218
format.set_branch_format(
219
branch.network_format_registry.get(branch_name))
221
raise errors.UnexpectedSmartServerResponse(response)
224
def create_repository(self, shared=False):
225
# as per meta1 formats - just delegate to the format object which may
227
result = self._format.repository_format.initialize(self, shared)
228
if not isinstance(result, RemoteRepository):
229
return self.open_repository()
233
def destroy_repository(self):
234
"""See BzrDir.destroy_repository"""
236
self._real_bzrdir.destroy_repository()
238
def create_branch(self):
239
# as per meta1 formats - just delegate to the format object which may
241
real_branch = self._format.get_branch_format().initialize(self)
242
if not isinstance(real_branch, RemoteBranch):
243
result = RemoteBranch(self, self.find_repository(), real_branch)
246
# BzrDir.clone_on_transport() uses the result of create_branch but does
247
# not return it to its callers; we save approximately 8% of our round
248
# trips by handing the branch we created back to the first caller to
249
# open_branch rather than probing anew. Long term we need a API in
250
# bzrdir that doesn't discard result objects (like result_branch).
252
self._next_open_branch_result = result
255
def destroy_branch(self):
256
"""See BzrDir.destroy_branch"""
258
self._real_bzrdir.destroy_branch()
259
self._next_open_branch_result = None
261
def create_workingtree(self, revision_id=None, from_branch=None):
262
raise errors.NotLocalUrl(self.transport.base)
264
def find_branch_format(self):
265
"""Find the branch 'format' for this bzrdir.
267
This might be a synthetic object for e.g. RemoteBranch and SVN.
269
b = self.open_branch()
272
def get_branch_reference(self):
273
"""See BzrDir.get_branch_reference()."""
274
response = self._get_branch_reference()
275
if response[0] == 'ref':
280
def _get_branch_reference(self):
281
path = self._path_for_remote_call(self._client)
282
medium = self._client._medium
283
if not medium._is_remote_before((1, 13)):
285
response = self._call('BzrDir.open_branchV2', path)
286
if response[0] not in ('ref', 'branch'):
287
raise errors.UnexpectedSmartServerResponse(response)
289
except errors.UnknownSmartMethod:
290
medium._remember_remote_is_before((1, 13))
291
response = self._call('BzrDir.open_branch', path)
292
if response[0] != 'ok':
293
raise errors.UnexpectedSmartServerResponse(response)
294
if response[1] != '':
295
return ('ref', response[1])
297
return ('branch', '')
299
def _get_tree_branch(self):
300
"""See BzrDir._get_tree_branch()."""
301
return None, self.open_branch()
303
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
305
raise NotImplementedError('unsupported flag support not implemented yet.')
306
if self._next_open_branch_result is not None:
307
# See create_branch for details.
308
result = self._next_open_branch_result
309
self._next_open_branch_result = None
311
response = self._get_branch_reference()
312
if response[0] == 'ref':
313
# a branch reference, use the existing BranchReference logic.
314
format = BranchReferenceFormat()
315
return format.open(self, _found=True, location=response[1],
316
ignore_fallbacks=ignore_fallbacks)
317
branch_format_name = response[1]
318
if not branch_format_name:
319
branch_format_name = None
320
format = RemoteBranchFormat(network_name=branch_format_name)
321
return RemoteBranch(self, self.find_repository(), format=format,
322
setup_stacking=not ignore_fallbacks)
324
def _open_repo_v1(self, path):
325
verb = 'BzrDir.find_repository'
326
response = self._call(verb, path)
327
if response[0] != 'ok':
328
raise errors.UnexpectedSmartServerResponse(response)
329
# servers that only support the v1 method don't support external
332
repo = self._real_bzrdir.open_repository()
333
response = response + ('no', repo._format.network_name())
334
return response, repo
336
def _open_repo_v2(self, path):
337
verb = 'BzrDir.find_repositoryV2'
338
response = self._call(verb, path)
339
if response[0] != 'ok':
340
raise errors.UnexpectedSmartServerResponse(response)
342
repo = self._real_bzrdir.open_repository()
343
response = response + (repo._format.network_name(),)
344
return response, repo
346
def _open_repo_v3(self, path):
347
verb = 'BzrDir.find_repositoryV3'
348
medium = self._client._medium
349
if medium._is_remote_before((1, 13)):
350
raise errors.UnknownSmartMethod(verb)
352
response = self._call(verb, path)
353
except errors.UnknownSmartMethod:
354
medium._remember_remote_is_before((1, 13))
356
if response[0] != 'ok':
357
raise errors.UnexpectedSmartServerResponse(response)
358
return response, None
360
def open_repository(self):
361
path = self._path_for_remote_call(self._client)
363
for probe in [self._open_repo_v3, self._open_repo_v2,
366
response, real_repo = probe(path)
368
except errors.UnknownSmartMethod:
371
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
372
if response[0] != 'ok':
373
raise errors.UnexpectedSmartServerResponse(response)
374
if len(response) != 6:
375
raise SmartProtocolError('incorrect response length %s' % (response,))
376
if response[1] == '':
377
# repo is at this dir.
378
format = response_tuple_to_repo_format(response[2:])
379
# Used to support creating a real format instance when needed.
380
format._creating_bzrdir = self
381
remote_repo = RemoteRepository(self, format)
382
format._creating_repo = remote_repo
383
if real_repo is not None:
384
remote_repo._set_real_repository(real_repo)
387
raise errors.NoRepositoryPresent(self)
389
def has_workingtree(self):
390
if self._has_working_tree is None:
392
self._has_working_tree = self._real_bzrdir.has_workingtree()
393
return self._has_working_tree
395
def open_workingtree(self, recommend_upgrade=True):
396
if self.has_workingtree():
397
raise errors.NotLocalUrl(self.root_transport)
399
raise errors.NoWorkingTree(self.root_transport.base)
401
def _path_for_remote_call(self, client):
402
"""Return the path to be used for this bzrdir in a remote call."""
403
return client.remote_path_from_transport(self.root_transport)
405
def get_branch_transport(self, branch_format):
407
return self._real_bzrdir.get_branch_transport(branch_format)
409
def get_repository_transport(self, repository_format):
411
return self._real_bzrdir.get_repository_transport(repository_format)
413
def get_workingtree_transport(self, workingtree_format):
415
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
417
def can_convert_format(self):
418
"""Upgrading of remote bzrdirs is not supported yet."""
421
def needs_format_conversion(self, format=None):
422
"""Upgrading of remote bzrdirs is not supported yet."""
424
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
425
% 'needs_format_conversion(format=None)')
428
def clone(self, url, revision_id=None, force_new_repo=False,
429
preserve_stacking=False):
431
return self._real_bzrdir.clone(url, revision_id=revision_id,
432
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
434
def _get_config(self):
435
return RemoteBzrDirConfig(self)
438
class RemoteRepositoryFormat(repository.RepositoryFormat):
439
"""Format for repositories accessed over a _SmartClient.
441
Instances of this repository are represented by RemoteRepository
444
The RemoteRepositoryFormat is parameterized during construction
445
to reflect the capabilities of the real, remote format. Specifically
446
the attributes rich_root_data and supports_tree_reference are set
447
on a per instance basis, and are not set (and should not be) at
450
:ivar _custom_format: If set, a specific concrete repository format that
451
will be used when initializing a repository with this
452
RemoteRepositoryFormat.
453
:ivar _creating_repo: If set, the repository object that this
454
RemoteRepositoryFormat was created for: it can be called into
455
to obtain data like the network name.
458
_matchingbzrdir = RemoteBzrDirFormat()
461
repository.RepositoryFormat.__init__(self)
462
self._custom_format = None
463
self._network_name = None
464
self._creating_bzrdir = None
465
self._supports_chks = None
466
self._supports_external_lookups = None
467
self._supports_tree_reference = None
468
self._rich_root_data = None
471
return "%s(_network_name=%r)" % (self.__class__.__name__,
475
def fast_deltas(self):
477
return self._custom_format.fast_deltas
480
def rich_root_data(self):
481
if self._rich_root_data is None:
483
self._rich_root_data = self._custom_format.rich_root_data
484
return self._rich_root_data
487
def supports_chks(self):
488
if self._supports_chks is None:
490
self._supports_chks = self._custom_format.supports_chks
491
return self._supports_chks
494
def supports_external_lookups(self):
495
if self._supports_external_lookups is None:
497
self._supports_external_lookups = \
498
self._custom_format.supports_external_lookups
499
return self._supports_external_lookups
502
def supports_tree_reference(self):
503
if self._supports_tree_reference is None:
505
self._supports_tree_reference = \
506
self._custom_format.supports_tree_reference
507
return self._supports_tree_reference
509
def _vfs_initialize(self, a_bzrdir, shared):
510
"""Helper for common code in initialize."""
511
if self._custom_format:
512
# Custom format requested
513
result = self._custom_format.initialize(a_bzrdir, shared=shared)
514
elif self._creating_bzrdir is not None:
515
# Use the format that the repository we were created to back
517
prior_repo = self._creating_bzrdir.open_repository()
518
prior_repo._ensure_real()
519
result = prior_repo._real_repository._format.initialize(
520
a_bzrdir, shared=shared)
522
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
523
# support remote initialization.
524
# We delegate to a real object at this point (as RemoteBzrDir
525
# delegate to the repository format which would lead to infinite
526
# recursion if we just called a_bzrdir.create_repository.
527
a_bzrdir._ensure_real()
528
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
529
if not isinstance(result, RemoteRepository):
530
return self.open(a_bzrdir)
534
def initialize(self, a_bzrdir, shared=False):
535
# Being asked to create on a non RemoteBzrDir:
536
if not isinstance(a_bzrdir, RemoteBzrDir):
537
return self._vfs_initialize(a_bzrdir, shared)
538
medium = a_bzrdir._client._medium
539
if medium._is_remote_before((1, 13)):
540
return self._vfs_initialize(a_bzrdir, shared)
541
# Creating on a remote bzr dir.
542
# 1) get the network name to use.
543
if self._custom_format:
544
network_name = self._custom_format.network_name()
545
elif self._network_name:
546
network_name = self._network_name
548
# Select the current bzrlib default and ask for that.
549
reference_bzrdir_format = bzrdir.format_registry.get('default')()
550
reference_format = reference_bzrdir_format.repository_format
551
network_name = reference_format.network_name()
552
# 2) try direct creation via RPC
553
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
554
verb = 'BzrDir.create_repository'
560
response = a_bzrdir._call(verb, path, network_name, shared_str)
561
except errors.UnknownSmartMethod:
562
# Fallback - use vfs methods
563
medium._remember_remote_is_before((1, 13))
564
return self._vfs_initialize(a_bzrdir, shared)
566
# Turn the response into a RemoteRepository object.
567
format = response_tuple_to_repo_format(response[1:])
568
# Used to support creating a real format instance when needed.
569
format._creating_bzrdir = a_bzrdir
570
remote_repo = RemoteRepository(a_bzrdir, format)
571
format._creating_repo = remote_repo
574
def open(self, a_bzrdir):
575
if not isinstance(a_bzrdir, RemoteBzrDir):
576
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
577
return a_bzrdir.open_repository()
579
def _ensure_real(self):
580
if self._custom_format is None:
581
self._custom_format = repository.network_format_registry.get(
585
def _fetch_order(self):
587
return self._custom_format._fetch_order
590
def _fetch_uses_deltas(self):
592
return self._custom_format._fetch_uses_deltas
595
def _fetch_reconcile(self):
597
return self._custom_format._fetch_reconcile
599
def get_format_description(self):
600
return 'bzr remote repository'
602
def __eq__(self, other):
603
return self.__class__ is other.__class__
605
def network_name(self):
606
if self._network_name:
607
return self._network_name
608
self._creating_repo._ensure_real()
609
return self._creating_repo._real_repository._format.network_name()
612
def pack_compresses(self):
614
return self._custom_format.pack_compresses
617
def _serializer(self):
619
return self._custom_format._serializer
622
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
623
"""Repository accessed over rpc.
625
For the moment most operations are performed using local transport-backed
629
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
630
"""Create a RemoteRepository instance.
632
:param remote_bzrdir: The bzrdir hosting this repository.
633
:param format: The RemoteFormat object to use.
634
:param real_repository: If not None, a local implementation of the
635
repository logic for the repository, usually accessing the data
637
:param _client: Private testing parameter - override the smart client
638
to be used by the repository.
641
self._real_repository = real_repository
643
self._real_repository = None
644
self.bzrdir = remote_bzrdir
646
self._client = remote_bzrdir._client
648
self._client = _client
649
self._format = format
650
self._lock_mode = None
651
self._lock_token = None
653
self._leave_lock = False
654
# Cache of revision parents; misses are cached during read locks, and
655
# write locks when no _real_repository has been set.
656
self._unstacked_provider = graph.CachingParentsProvider(
657
get_parent_map=self._get_parent_map_rpc)
658
self._unstacked_provider.disable_cache()
660
# These depend on the actual remote format, so force them off for
661
# maximum compatibility. XXX: In future these should depend on the
662
# remote repository instance, but this is irrelevant until we perform
663
# reconcile via an RPC call.
664
self._reconcile_does_inventory_gc = False
665
self._reconcile_fixes_text_parents = False
666
self._reconcile_backsup_inventory = False
667
self.base = self.bzrdir.transport.base
668
# Additional places to query for data.
669
self._fallback_repositories = []
672
return "%s(%s)" % (self.__class__.__name__, self.base)
676
def abort_write_group(self, suppress_errors=False):
677
"""Complete a write group on the decorated repository.
679
Smart methods perform operations in a single step so this API
680
is not really applicable except as a compatibility thunk
681
for older plugins that don't use e.g. the CommitBuilder
684
:param suppress_errors: see Repository.abort_write_group.
687
return self._real_repository.abort_write_group(
688
suppress_errors=suppress_errors)
692
"""Decorate the real repository for now.
694
In the long term a full blown network facility is needed to avoid
695
creating a real repository object locally.
698
return self._real_repository.chk_bytes
700
def commit_write_group(self):
701
"""Complete a write group on the decorated repository.
703
Smart methods perform operations in a single step so this API
704
is not really applicable except as a compatibility thunk
705
for older plugins that don't use e.g. the CommitBuilder
709
return self._real_repository.commit_write_group()
711
def resume_write_group(self, tokens):
713
return self._real_repository.resume_write_group(tokens)
715
def suspend_write_group(self):
717
return self._real_repository.suspend_write_group()
719
def get_missing_parent_inventories(self, check_for_missing_texts=True):
721
return self._real_repository.get_missing_parent_inventories(
722
check_for_missing_texts=check_for_missing_texts)
724
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
726
return self._real_repository.get_rev_id_for_revno(
729
def get_rev_id_for_revno(self, revno, known_pair):
730
"""See Repository.get_rev_id_for_revno."""
731
path = self.bzrdir._path_for_remote_call(self._client)
733
if self._client._medium._is_remote_before((1, 17)):
734
return self._get_rev_id_for_revno_vfs(revno, known_pair)
735
response = self._call(
736
'Repository.get_rev_id_for_revno', path, revno, known_pair)
737
except errors.UnknownSmartMethod:
738
self._client._medium._remember_remote_is_before((1, 17))
739
return self._get_rev_id_for_revno_vfs(revno, known_pair)
740
if response[0] == 'ok':
741
return True, response[1]
742
elif response[0] == 'history-incomplete':
743
known_pair = response[1:3]
744
for fallback in self._fallback_repositories:
745
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
750
# Not found in any fallbacks
751
return False, known_pair
753
raise errors.UnexpectedSmartServerResponse(response)
755
def _ensure_real(self):
756
"""Ensure that there is a _real_repository set.
758
Used before calls to self._real_repository.
760
Note that _ensure_real causes many roundtrips to the server which are
761
not desirable, and prevents the use of smart one-roundtrip RPC's to
762
perform complex operations (such as accessing parent data, streaming
763
revisions etc). Adding calls to _ensure_real should only be done when
764
bringing up new functionality, adding fallbacks for smart methods that
765
require a fallback path, and never to replace an existing smart method
766
invocation. If in doubt chat to the bzr network team.
768
if self._real_repository is None:
769
if 'hpssvfs' in debug.debug_flags:
771
warning('VFS Repository access triggered\n%s',
772
''.join(traceback.format_stack()))
773
self._unstacked_provider.missing_keys.clear()
774
self.bzrdir._ensure_real()
775
self._set_real_repository(
776
self.bzrdir._real_bzrdir.open_repository())
778
def _translate_error(self, err, **context):
779
self.bzrdir._translate_error(err, repository=self, **context)
781
def find_text_key_references(self):
782
"""Find the text key references within the repository.
784
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
785
revision_ids. Each altered file-ids has the exact revision_ids that
786
altered it listed explicitly.
787
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
788
to whether they were referred to by the inventory of the
789
revision_id that they contain. The inventory texts from all present
790
revision ids are assessed to generate this report.
793
return self._real_repository.find_text_key_references()
795
def _generate_text_key_index(self):
796
"""Generate a new text key index for the repository.
798
This is an expensive function that will take considerable time to run.
800
:return: A dict mapping (file_id, revision_id) tuples to a list of
801
parents, also (file_id, revision_id) tuples.
804
return self._real_repository._generate_text_key_index()
806
def _get_revision_graph(self, revision_id):
807
"""Private method for using with old (< 1.2) servers to fallback."""
808
if revision_id is None:
810
elif revision.is_null(revision_id):
813
path = self.bzrdir._path_for_remote_call(self._client)
814
response = self._call_expecting_body(
815
'Repository.get_revision_graph', path, revision_id)
816
response_tuple, response_handler = response
817
if response_tuple[0] != 'ok':
818
raise errors.UnexpectedSmartServerResponse(response_tuple)
819
coded = response_handler.read_body_bytes()
821
# no revisions in this repository!
823
lines = coded.split('\n')
826
d = tuple(line.split())
827
revision_graph[d[0]] = d[1:]
829
return revision_graph
832
"""See Repository._get_sink()."""
833
return RemoteStreamSink(self)
835
def _get_source(self, to_format):
836
"""Return a source for streaming from this repository."""
837
return RemoteStreamSource(self, to_format)
840
def has_revision(self, revision_id):
841
"""True if this repository has a copy of the revision."""
842
# Copy of bzrlib.repository.Repository.has_revision
843
return revision_id in self.has_revisions((revision_id,))
846
def has_revisions(self, revision_ids):
847
"""Probe to find out the presence of multiple revisions.
849
:param revision_ids: An iterable of revision_ids.
850
:return: A set of the revision_ids that were present.
852
# Copy of bzrlib.repository.Repository.has_revisions
853
parent_map = self.get_parent_map(revision_ids)
854
result = set(parent_map)
855
if _mod_revision.NULL_REVISION in revision_ids:
856
result.add(_mod_revision.NULL_REVISION)
859
def _has_same_fallbacks(self, other_repo):
860
"""Returns true if the repositories have the same fallbacks."""
861
# XXX: copied from Repository; it should be unified into a base class
862
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
863
my_fb = self._fallback_repositories
864
other_fb = other_repo._fallback_repositories
865
if len(my_fb) != len(other_fb):
867
for f, g in zip(my_fb, other_fb):
868
if not f.has_same_location(g):
872
def has_same_location(self, other):
873
# TODO: Move to RepositoryBase and unify with the regular Repository
874
# one; unfortunately the tests rely on slightly different behaviour at
875
# present -- mbp 20090710
876
return (self.__class__ is other.__class__ and
877
self.bzrdir.transport.base == other.bzrdir.transport.base)
879
def get_graph(self, other_repository=None):
880
"""Return the graph for this repository format"""
881
parents_provider = self._make_parents_provider(other_repository)
882
return graph.Graph(parents_provider)
884
def gather_stats(self, revid=None, committers=None):
885
"""See Repository.gather_stats()."""
886
path = self.bzrdir._path_for_remote_call(self._client)
887
# revid can be None to indicate no revisions, not just NULL_REVISION
888
if revid is None or revision.is_null(revid):
892
if committers is None or not committers:
893
fmt_committers = 'no'
895
fmt_committers = 'yes'
896
response_tuple, response_handler = self._call_expecting_body(
897
'Repository.gather_stats', path, fmt_revid, fmt_committers)
898
if response_tuple[0] != 'ok':
899
raise errors.UnexpectedSmartServerResponse(response_tuple)
901
body = response_handler.read_body_bytes()
903
for line in body.split('\n'):
906
key, val_text = line.split(':')
907
if key in ('revisions', 'size', 'committers'):
908
result[key] = int(val_text)
909
elif key in ('firstrev', 'latestrev'):
910
values = val_text.split(' ')[1:]
911
result[key] = (float(values[0]), long(values[1]))
915
def find_branches(self, using=False):
916
"""See Repository.find_branches()."""
917
# should be an API call to the server.
919
return self._real_repository.find_branches(using=using)
921
def get_physical_lock_status(self):
922
"""See Repository.get_physical_lock_status()."""
923
# should be an API call to the server.
925
return self._real_repository.get_physical_lock_status()
927
def is_in_write_group(self):
928
"""Return True if there is an open write group.
930
write groups are only applicable locally for the smart server..
932
if self._real_repository:
933
return self._real_repository.is_in_write_group()
936
return self._lock_count >= 1
939
"""See Repository.is_shared()."""
940
path = self.bzrdir._path_for_remote_call(self._client)
941
response = self._call('Repository.is_shared', path)
942
if response[0] not in ('yes', 'no'):
943
raise SmartProtocolError('unexpected response code %s' % (response,))
944
return response[0] == 'yes'
946
def is_write_locked(self):
947
return self._lock_mode == 'w'
950
# wrong eventually - want a local lock cache context
951
if not self._lock_mode:
953
self._lock_mode = 'r'
955
self._unstacked_provider.enable_cache(cache_misses=True)
956
if self._real_repository is not None:
957
self._real_repository.lock_read()
958
for repo in self._fallback_repositories:
961
self._lock_count += 1
963
def _remote_lock_write(self, token):
964
path = self.bzrdir._path_for_remote_call(self._client)
967
err_context = {'token': token}
968
response = self._call('Repository.lock_write', path, token,
970
if response[0] == 'ok':
974
raise errors.UnexpectedSmartServerResponse(response)
976
def lock_write(self, token=None, _skip_rpc=False):
977
if not self._lock_mode:
980
if self._lock_token is not None:
981
if token != self._lock_token:
982
raise errors.TokenMismatch(token, self._lock_token)
983
self._lock_token = token
985
self._lock_token = self._remote_lock_write(token)
986
# if self._lock_token is None, then this is something like packs or
987
# svn where we don't get to lock the repo, or a weave style repository
988
# where we cannot lock it over the wire and attempts to do so will
990
if self._real_repository is not None:
991
self._real_repository.lock_write(token=self._lock_token)
992
if token is not None:
993
self._leave_lock = True
995
self._leave_lock = False
996
self._lock_mode = 'w'
998
cache_misses = self._real_repository is None
999
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1000
for repo in self._fallback_repositories:
1001
# Writes don't affect fallback repos
1003
elif self._lock_mode == 'r':
1004
raise errors.ReadOnlyError(self)
1006
self._lock_count += 1
1007
return self._lock_token or None
1009
def leave_lock_in_place(self):
1010
if not self._lock_token:
1011
raise NotImplementedError(self.leave_lock_in_place)
1012
self._leave_lock = True
1014
def dont_leave_lock_in_place(self):
1015
if not self._lock_token:
1016
raise NotImplementedError(self.dont_leave_lock_in_place)
1017
self._leave_lock = False
1019
def _set_real_repository(self, repository):
1020
"""Set the _real_repository for this repository.
1022
:param repository: The repository to fallback to for non-hpss
1023
implemented operations.
1025
if self._real_repository is not None:
1026
# Replacing an already set real repository.
1027
# We cannot do this [currently] if the repository is locked -
1028
# synchronised state might be lost.
1029
if self.is_locked():
1030
raise AssertionError('_real_repository is already set')
1031
if isinstance(repository, RemoteRepository):
1032
raise AssertionError()
1033
self._real_repository = repository
1034
# three code paths happen here:
1035
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1036
# up stacking. In this case self._fallback_repositories is [], and the
1037
# real repo is already setup. Preserve the real repo and
1038
# RemoteRepository.add_fallback_repository will avoid adding
1040
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1041
# ensure_real is triggered from a branch, the real repository to
1042
# set already has a matching list with separate instances, but
1043
# as they are also RemoteRepositories we don't worry about making the
1044
# lists be identical.
1045
# 3) new servers, RemoteRepository.ensure_real is triggered before
1046
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1047
# and need to populate it.
1048
if (self._fallback_repositories and
1049
len(self._real_repository._fallback_repositories) !=
1050
len(self._fallback_repositories)):
1051
if len(self._real_repository._fallback_repositories):
1052
raise AssertionError(
1053
"cannot cleanly remove existing _fallback_repositories")
1054
for fb in self._fallback_repositories:
1055
self._real_repository.add_fallback_repository(fb)
1056
if self._lock_mode == 'w':
1057
# if we are already locked, the real repository must be able to
1058
# acquire the lock with our token.
1059
self._real_repository.lock_write(self._lock_token)
1060
elif self._lock_mode == 'r':
1061
self._real_repository.lock_read()
1063
def start_write_group(self):
1064
"""Start a write group on the decorated repository.
1066
Smart methods perform operations in a single step so this API
1067
is not really applicable except as a compatibility thunk
1068
for older plugins that don't use e.g. the CommitBuilder
1072
return self._real_repository.start_write_group()
1074
def _unlock(self, token):
1075
path = self.bzrdir._path_for_remote_call(self._client)
1077
# with no token the remote repository is not persistently locked.
1079
err_context = {'token': token}
1080
response = self._call('Repository.unlock', path, token,
1082
if response == ('ok',):
1085
raise errors.UnexpectedSmartServerResponse(response)
1087
@only_raises(errors.LockNotHeld, errors.LockBroken)
1089
if not self._lock_count:
1090
return lock.cant_unlock_not_held(self)
1091
self._lock_count -= 1
1092
if self._lock_count > 0:
1094
self._unstacked_provider.disable_cache()
1095
old_mode = self._lock_mode
1096
self._lock_mode = None
1098
# The real repository is responsible at present for raising an
1099
# exception if it's in an unfinished write group. However, it
1100
# normally will *not* actually remove the lock from disk - that's
1101
# done by the server on receiving the Repository.unlock call.
1102
# This is just to let the _real_repository stay up to date.
1103
if self._real_repository is not None:
1104
self._real_repository.unlock()
1106
# The rpc-level lock should be released even if there was a
1107
# problem releasing the vfs-based lock.
1109
# Only write-locked repositories need to make a remote method
1110
# call to perform the unlock.
1111
old_token = self._lock_token
1112
self._lock_token = None
1113
if not self._leave_lock:
1114
self._unlock(old_token)
1115
# Fallbacks are always 'lock_read()' so we don't pay attention to
1117
for repo in self._fallback_repositories:
1120
def break_lock(self):
1121
# should hand off to the network
1123
return self._real_repository.break_lock()
1125
def _get_tarball(self, compression):
1126
"""Return a TemporaryFile containing a repository tarball.
1128
Returns None if the server does not support sending tarballs.
1131
path = self.bzrdir._path_for_remote_call(self._client)
1133
response, protocol = self._call_expecting_body(
1134
'Repository.tarball', path, compression)
1135
except errors.UnknownSmartMethod:
1136
protocol.cancel_read_body()
1138
if response[0] == 'ok':
1139
# Extract the tarball and return it
1140
t = tempfile.NamedTemporaryFile()
1141
# TODO: rpc layer should read directly into it...
1142
t.write(protocol.read_body_bytes())
1145
raise errors.UnexpectedSmartServerResponse(response)
1147
def sprout(self, to_bzrdir, revision_id=None):
1148
# TODO: Option to control what format is created?
1150
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1152
dest_repo.fetch(self, revision_id=revision_id)
1155
### These methods are just thin shims to the VFS object for now.
1157
def revision_tree(self, revision_id):
1159
return self._real_repository.revision_tree(revision_id)
1161
def get_serializer_format(self):
1163
return self._real_repository.get_serializer_format()
1165
def get_commit_builder(self, branch, parents, config, timestamp=None,
1166
timezone=None, committer=None, revprops=None,
1168
# FIXME: It ought to be possible to call this without immediately
1169
# triggering _ensure_real. For now it's the easiest thing to do.
1171
real_repo = self._real_repository
1172
builder = real_repo.get_commit_builder(branch, parents,
1173
config, timestamp=timestamp, timezone=timezone,
1174
committer=committer, revprops=revprops, revision_id=revision_id)
1177
def add_fallback_repository(self, repository):
1178
"""Add a repository to use for looking up data not held locally.
1180
:param repository: A repository.
1182
if not self._format.supports_external_lookups:
1183
raise errors.UnstackableRepositoryFormat(
1184
self._format.network_name(), self.base)
1185
# We need to accumulate additional repositories here, to pass them in
1188
if self.is_locked():
1189
# We will call fallback.unlock() when we transition to the unlocked
1190
# state, so always add a lock here. If a caller passes us a locked
1191
# repository, they are responsible for unlocking it later.
1192
repository.lock_read()
1193
self._fallback_repositories.append(repository)
1194
# If self._real_repository was parameterised already (e.g. because a
1195
# _real_branch had its get_stacked_on_url method called), then the
1196
# repository to be added may already be in the _real_repositories list.
1197
if self._real_repository is not None:
1198
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1199
self._real_repository._fallback_repositories]
1200
if repository.bzrdir.root_transport.base not in fallback_locations:
1201
self._real_repository.add_fallback_repository(repository)
1203
def add_inventory(self, revid, inv, parents):
1205
return self._real_repository.add_inventory(revid, inv, parents)
1207
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1210
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1211
delta, new_revision_id, parents)
1213
def add_revision(self, rev_id, rev, inv=None, config=None):
1215
return self._real_repository.add_revision(
1216
rev_id, rev, inv=inv, config=config)
1219
def get_inventory(self, revision_id):
1221
return self._real_repository.get_inventory(revision_id)
1223
def iter_inventories(self, revision_ids, ordering=None):
1225
return self._real_repository.iter_inventories(revision_ids, ordering)
1228
def get_revision(self, revision_id):
1230
return self._real_repository.get_revision(revision_id)
1232
def get_transaction(self):
1234
return self._real_repository.get_transaction()
1237
def clone(self, a_bzrdir, revision_id=None):
1239
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1241
def make_working_trees(self):
1242
"""See Repository.make_working_trees"""
1244
return self._real_repository.make_working_trees()
1246
def refresh_data(self):
1247
"""Re-read any data needed to to synchronise with disk.
1249
This method is intended to be called after another repository instance
1250
(such as one used by a smart server) has inserted data into the
1251
repository. It may not be called during a write group, but may be
1252
called at any other time.
1254
if self.is_in_write_group():
1255
raise errors.InternalBzrError(
1256
"May not refresh_data while in a write group.")
1257
if self._real_repository is not None:
1258
self._real_repository.refresh_data()
1260
def revision_ids_to_search_result(self, result_set):
1261
"""Convert a set of revision ids to a graph SearchResult."""
1262
result_parents = set()
1263
for parents in self.get_graph().get_parent_map(
1264
result_set).itervalues():
1265
result_parents.update(parents)
1266
included_keys = result_set.intersection(result_parents)
1267
start_keys = result_set.difference(included_keys)
1268
exclude_keys = result_parents.difference(result_set)
1269
result = graph.SearchResult(start_keys, exclude_keys,
1270
len(result_set), result_set)
1274
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1275
"""Return the revision ids that other has that this does not.
1277
These are returned in topological order.
1279
revision_id: only return revision ids included by revision_id.
1281
return repository.InterRepository.get(
1282
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1284
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1286
# No base implementation to use as RemoteRepository is not a subclass
1287
# of Repository; so this is a copy of Repository.fetch().
1288
if fetch_spec is not None and revision_id is not None:
1289
raise AssertionError(
1290
"fetch_spec and revision_id are mutually exclusive.")
1291
if self.is_in_write_group():
1292
raise errors.InternalBzrError(
1293
"May not fetch while in a write group.")
1294
# fast path same-url fetch operations
1295
if (self.has_same_location(source)
1296
and fetch_spec is None
1297
and self._has_same_fallbacks(source)):
1298
# check that last_revision is in 'from' and then return a
1300
if (revision_id is not None and
1301
not revision.is_null(revision_id)):
1302
self.get_revision(revision_id)
1304
# if there is no specific appropriate InterRepository, this will get
1305
# the InterRepository base class, which raises an
1306
# IncompatibleRepositories when asked to fetch.
1307
inter = repository.InterRepository.get(source, self)
1308
return inter.fetch(revision_id=revision_id, pb=pb,
1309
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1311
def create_bundle(self, target, base, fileobj, format=None):
1313
self._real_repository.create_bundle(target, base, fileobj, format)
1316
def get_ancestry(self, revision_id, topo_sorted=True):
1318
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1320
def fileids_altered_by_revision_ids(self, revision_ids):
1322
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1324
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1326
return self._real_repository._get_versioned_file_checker(
1327
revisions, revision_versions_cache)
1329
def iter_files_bytes(self, desired_files):
1330
"""See Repository.iter_file_bytes.
1333
return self._real_repository.iter_files_bytes(desired_files)
1335
def get_parent_map(self, revision_ids):
1336
"""See bzrlib.Graph.get_parent_map()."""
1337
return self._make_parents_provider().get_parent_map(revision_ids)
1339
def _get_parent_map_rpc(self, keys):
1340
"""Helper for get_parent_map that performs the RPC."""
1341
medium = self._client._medium
1342
if medium._is_remote_before((1, 2)):
1343
# We already found out that the server can't understand
1344
# Repository.get_parent_map requests, so just fetch the whole
1347
# Note that this reads the whole graph, when only some keys are
1348
# wanted. On this old server there's no way (?) to get them all
1349
# in one go, and the user probably will have seen a warning about
1350
# the server being old anyhow.
1351
rg = self._get_revision_graph(None)
1352
# There is an API discrepancy between get_parent_map and
1353
# get_revision_graph. Specifically, a "key:()" pair in
1354
# get_revision_graph just means a node has no parents. For
1355
# "get_parent_map" it means the node is a ghost. So fix up the
1356
# graph to correct this.
1357
# https://bugs.launchpad.net/bzr/+bug/214894
1358
# There is one other "bug" which is that ghosts in
1359
# get_revision_graph() are not returned at all. But we won't worry
1360
# about that for now.
1361
for node_id, parent_ids in rg.iteritems():
1362
if parent_ids == ():
1363
rg[node_id] = (NULL_REVISION,)
1364
rg[NULL_REVISION] = ()
1369
raise ValueError('get_parent_map(None) is not valid')
1370
if NULL_REVISION in keys:
1371
keys.discard(NULL_REVISION)
1372
found_parents = {NULL_REVISION:()}
1374
return found_parents
1377
# TODO(Needs analysis): We could assume that the keys being requested
1378
# from get_parent_map are in a breadth first search, so typically they
1379
# will all be depth N from some common parent, and we don't have to
1380
# have the server iterate from the root parent, but rather from the
1381
# keys we're searching; and just tell the server the keyspace we
1382
# already have; but this may be more traffic again.
1384
# Transform self._parents_map into a search request recipe.
1385
# TODO: Manage this incrementally to avoid covering the same path
1386
# repeatedly. (The server will have to on each request, but the less
1387
# work done the better).
1389
# Negative caching notes:
1390
# new server sends missing when a request including the revid
1391
# 'include-missing:' is present in the request.
1392
# missing keys are serialised as missing:X, and we then call
1393
# provider.note_missing(X) for-all X
1394
parents_map = self._unstacked_provider.get_cached_map()
1395
if parents_map is None:
1396
# Repository is not locked, so there's no cache.
1398
# start_set is all the keys in the cache
1399
start_set = set(parents_map)
1400
# result set is all the references to keys in the cache
1401
result_parents = set()
1402
for parents in parents_map.itervalues():
1403
result_parents.update(parents)
1404
stop_keys = result_parents.difference(start_set)
1405
# We don't need to send ghosts back to the server as a position to
1407
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1408
key_count = len(parents_map)
1409
if (NULL_REVISION in result_parents
1410
and NULL_REVISION in self._unstacked_provider.missing_keys):
1411
# If we pruned NULL_REVISION from the stop_keys because it's also
1412
# in our cache of "missing" keys we need to increment our key count
1413
# by 1, because the reconsitituted SearchResult on the server will
1414
# still consider NULL_REVISION to be an included key.
1416
included_keys = start_set.intersection(result_parents)
1417
start_set.difference_update(included_keys)
1418
recipe = ('manual', start_set, stop_keys, key_count)
1419
body = self._serialise_search_recipe(recipe)
1420
path = self.bzrdir._path_for_remote_call(self._client)
1422
if type(key) is not str:
1424
"key %r not a plain string" % (key,))
1425
verb = 'Repository.get_parent_map'
1426
args = (path, 'include-missing:') + tuple(keys)
1428
response = self._call_with_body_bytes_expecting_body(
1430
except errors.UnknownSmartMethod:
1431
# Server does not support this method, so get the whole graph.
1432
# Worse, we have to force a disconnection, because the server now
1433
# doesn't realise it has a body on the wire to consume, so the
1434
# only way to recover is to abandon the connection.
1436
'Server is too old for fast get_parent_map, reconnecting. '
1437
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1439
# To avoid having to disconnect repeatedly, we keep track of the
1440
# fact the server doesn't understand remote methods added in 1.2.
1441
medium._remember_remote_is_before((1, 2))
1442
# Recurse just once and we should use the fallback code.
1443
return self._get_parent_map_rpc(keys)
1444
response_tuple, response_handler = response
1445
if response_tuple[0] not in ['ok']:
1446
response_handler.cancel_read_body()
1447
raise errors.UnexpectedSmartServerResponse(response_tuple)
1448
if response_tuple[0] == 'ok':
1449
coded = bz2.decompress(response_handler.read_body_bytes())
1451
# no revisions found
1453
lines = coded.split('\n')
1456
d = tuple(line.split())
1458
revision_graph[d[0]] = d[1:]
1461
if d[0].startswith('missing:'):
1463
self._unstacked_provider.note_missing_key(revid)
1465
# no parents - so give the Graph result
1467
revision_graph[d[0]] = (NULL_REVISION,)
1468
return revision_graph
1471
def get_signature_text(self, revision_id):
1473
return self._real_repository.get_signature_text(revision_id)
1476
def get_inventory_xml(self, revision_id):
1478
return self._real_repository.get_inventory_xml(revision_id)
1480
def deserialise_inventory(self, revision_id, xml):
1482
return self._real_repository.deserialise_inventory(revision_id, xml)
1484
def reconcile(self, other=None, thorough=False):
1486
return self._real_repository.reconcile(other=other, thorough=thorough)
1488
def all_revision_ids(self):
1490
return self._real_repository.all_revision_ids()
1493
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1495
return self._real_repository.get_deltas_for_revisions(revisions,
1496
specific_fileids=specific_fileids)
1499
def get_revision_delta(self, revision_id, specific_fileids=None):
1501
return self._real_repository.get_revision_delta(revision_id,
1502
specific_fileids=specific_fileids)
1505
def revision_trees(self, revision_ids):
1507
return self._real_repository.revision_trees(revision_ids)
1510
def get_revision_reconcile(self, revision_id):
1512
return self._real_repository.get_revision_reconcile(revision_id)
1515
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1517
return self._real_repository.check(revision_ids=revision_ids,
1518
callback_refs=callback_refs, check_repo=check_repo)
1520
def copy_content_into(self, destination, revision_id=None):
1522
return self._real_repository.copy_content_into(
1523
destination, revision_id=revision_id)
1525
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1526
# get a tarball of the remote repository, and copy from that into the
1528
from bzrlib import osutils
1530
# TODO: Maybe a progress bar while streaming the tarball?
1531
note("Copying repository content as tarball...")
1532
tar_file = self._get_tarball('bz2')
1533
if tar_file is None:
1535
destination = to_bzrdir.create_repository()
1537
tar = tarfile.open('repository', fileobj=tar_file,
1539
tmpdir = osutils.mkdtemp()
1541
_extract_tar(tar, tmpdir)
1542
tmp_bzrdir = BzrDir.open(tmpdir)
1543
tmp_repo = tmp_bzrdir.open_repository()
1544
tmp_repo.copy_content_into(destination, revision_id)
1546
osutils.rmtree(tmpdir)
1550
# TODO: Suggestion from john: using external tar is much faster than
1551
# python's tarfile library, but it may not work on windows.
1554
def inventories(self):
1555
"""Decorate the real repository for now.
1557
In the long term a full blown network facility is needed to
1558
avoid creating a real repository object locally.
1561
return self._real_repository.inventories
1564
def pack(self, hint=None):
1565
"""Compress the data within the repository.
1567
This is not currently implemented within the smart server.
1570
return self._real_repository.pack(hint=hint)
1573
def revisions(self):
1574
"""Decorate the real repository for now.
1576
In the short term this should become a real object to intercept graph
1579
In the long term a full blown network facility is needed.
1582
return self._real_repository.revisions
1584
def set_make_working_trees(self, new_value):
1586
new_value_str = "True"
1588
new_value_str = "False"
1589
path = self.bzrdir._path_for_remote_call(self._client)
1591
response = self._call(
1592
'Repository.set_make_working_trees', path, new_value_str)
1593
except errors.UnknownSmartMethod:
1595
self._real_repository.set_make_working_trees(new_value)
1597
if response[0] != 'ok':
1598
raise errors.UnexpectedSmartServerResponse(response)
1601
def signatures(self):
1602
"""Decorate the real repository for now.
1604
In the long term a full blown network facility is needed to avoid
1605
creating a real repository object locally.
1608
return self._real_repository.signatures
1611
def sign_revision(self, revision_id, gpg_strategy):
1613
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1617
"""Decorate the real repository for now.
1619
In the long term a full blown network facility is needed to avoid
1620
creating a real repository object locally.
1623
return self._real_repository.texts
1626
def get_revisions(self, revision_ids):
1628
return self._real_repository.get_revisions(revision_ids)
1630
def supports_rich_root(self):
1631
return self._format.rich_root_data
1633
def iter_reverse_revision_history(self, revision_id):
1635
return self._real_repository.iter_reverse_revision_history(revision_id)
1638
def _serializer(self):
1639
return self._format._serializer
1641
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1643
return self._real_repository.store_revision_signature(
1644
gpg_strategy, plaintext, revision_id)
1646
def add_signature_text(self, revision_id, signature):
1648
return self._real_repository.add_signature_text(revision_id, signature)
1650
def has_signature_for_revision_id(self, revision_id):
1652
return self._real_repository.has_signature_for_revision_id(revision_id)
1654
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1656
return self._real_repository.item_keys_introduced_by(revision_ids,
1657
_files_pb=_files_pb)
1659
def revision_graph_can_have_wrong_parents(self):
1660
# The answer depends on the remote repo format.
1662
return self._real_repository.revision_graph_can_have_wrong_parents()
1664
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1666
return self._real_repository._find_inconsistent_revision_parents(
1669
def _check_for_inconsistent_revision_parents(self):
1671
return self._real_repository._check_for_inconsistent_revision_parents()
1673
def _make_parents_provider(self, other=None):
1674
providers = [self._unstacked_provider]
1675
if other is not None:
1676
providers.insert(0, other)
1677
providers.extend(r._make_parents_provider() for r in
1678
self._fallback_repositories)
1679
return graph.StackedParentsProvider(providers)
1681
def _serialise_search_recipe(self, recipe):
1682
"""Serialise a graph search recipe.
1684
:param recipe: A search recipe (start, stop, count).
1685
:return: Serialised bytes.
1687
start_keys = ' '.join(recipe[1])
1688
stop_keys = ' '.join(recipe[2])
1689
count = str(recipe[3])
1690
return '\n'.join((start_keys, stop_keys, count))
1692
def _serialise_search_result(self, search_result):
1693
if isinstance(search_result, graph.PendingAncestryResult):
1694
parts = ['ancestry-of']
1695
parts.extend(search_result.heads)
1697
recipe = search_result.get_recipe()
1698
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1699
return '\n'.join(parts)
1702
path = self.bzrdir._path_for_remote_call(self._client)
1704
response = self._call('PackRepository.autopack', path)
1705
except errors.UnknownSmartMethod:
1707
self._real_repository._pack_collection.autopack()
1710
if response[0] != 'ok':
1711
raise errors.UnexpectedSmartServerResponse(response)
1714
class RemoteStreamSink(repository.StreamSink):
1716
def _insert_real(self, stream, src_format, resume_tokens):
1717
self.target_repo._ensure_real()
1718
sink = self.target_repo._real_repository._get_sink()
1719
result = sink.insert_stream(stream, src_format, resume_tokens)
1721
self.target_repo.autopack()
1724
def insert_stream(self, stream, src_format, resume_tokens):
1725
target = self.target_repo
1726
target._unstacked_provider.missing_keys.clear()
1727
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1728
if target._lock_token:
1729
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1730
lock_args = (target._lock_token or '',)
1732
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1734
client = target._client
1735
medium = client._medium
1736
path = target.bzrdir._path_for_remote_call(client)
1737
# Probe for the verb to use with an empty stream before sending the
1738
# real stream to it. We do this both to avoid the risk of sending a
1739
# large request that is then rejected, and because we don't want to
1740
# implement a way to buffer, rewind, or restart the stream.
1742
for verb, required_version in candidate_calls:
1743
if medium._is_remote_before(required_version):
1746
# We've already done the probing (and set _is_remote_before) on
1747
# a previous insert.
1750
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1752
response = client.call_with_body_stream(
1753
(verb, path, '') + lock_args, byte_stream)
1754
except errors.UnknownSmartMethod:
1755
medium._remember_remote_is_before(required_version)
1761
return self._insert_real(stream, src_format, resume_tokens)
1762
self._last_inv_record = None
1763
self._last_substream = None
1764
if required_version < (1, 19):
1765
# Remote side doesn't support inventory deltas. Wrap the stream to
1766
# make sure we don't send any. If the stream contains inventory
1767
# deltas we'll interrupt the smart insert_stream request and
1769
stream = self._stop_stream_if_inventory_delta(stream)
1770
byte_stream = smart_repo._stream_to_byte_stream(
1772
resume_tokens = ' '.join(resume_tokens)
1773
response = client.call_with_body_stream(
1774
(verb, path, resume_tokens) + lock_args, byte_stream)
1775
if response[0][0] not in ('ok', 'missing-basis'):
1776
raise errors.UnexpectedSmartServerResponse(response)
1777
if self._last_substream is not None:
1778
# The stream included an inventory-delta record, but the remote
1779
# side isn't new enough to support them. So we need to send the
1780
# rest of the stream via VFS.
1781
self.target_repo.refresh_data()
1782
return self._resume_stream_with_vfs(response, src_format)
1783
if response[0][0] == 'missing-basis':
1784
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1785
resume_tokens = tokens
1786
return resume_tokens, set(missing_keys)
1788
self.target_repo.refresh_data()
1791
def _resume_stream_with_vfs(self, response, src_format):
1792
"""Resume sending a stream via VFS, first resending the record and
1793
substream that couldn't be sent via an insert_stream verb.
1795
if response[0][0] == 'missing-basis':
1796
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1797
# Ignore missing_keys, we haven't finished inserting yet
1800
def resume_substream():
1801
# Yield the substream that was interrupted.
1802
for record in self._last_substream:
1804
self._last_substream = None
1805
def resume_stream():
1806
# Finish sending the interrupted substream
1807
yield ('inventory-deltas', resume_substream())
1808
# Then simply continue sending the rest of the stream.
1809
for substream_kind, substream in self._last_stream:
1810
yield substream_kind, substream
1811
return self._insert_real(resume_stream(), src_format, tokens)
1813
def _stop_stream_if_inventory_delta(self, stream):
1814
"""Normally this just lets the original stream pass-through unchanged.
1816
However if any 'inventory-deltas' substream occurs it will stop
1817
streaming, and store the interrupted substream and stream in
1818
self._last_substream and self._last_stream so that the stream can be
1819
resumed by _resume_stream_with_vfs.
1822
stream_iter = iter(stream)
1823
for substream_kind, substream in stream_iter:
1824
if substream_kind == 'inventory-deltas':
1825
self._last_substream = substream
1826
self._last_stream = stream_iter
1829
yield substream_kind, substream
1832
class RemoteStreamSource(repository.StreamSource):
1833
"""Stream data from a remote server."""
1835
def get_stream(self, search):
1836
if (self.from_repository._fallback_repositories and
1837
self.to_format._fetch_order == 'topological'):
1838
return self._real_stream(self.from_repository, search)
1841
repos = [self.from_repository]
1847
repos.extend(repo._fallback_repositories)
1848
sources.append(repo)
1849
return self.missing_parents_chain(search, sources)
1851
def get_stream_for_missing_keys(self, missing_keys):
1852
self.from_repository._ensure_real()
1853
real_repo = self.from_repository._real_repository
1854
real_source = real_repo._get_source(self.to_format)
1855
return real_source.get_stream_for_missing_keys(missing_keys)
1857
def _real_stream(self, repo, search):
1858
"""Get a stream for search from repo.
1860
This never called RemoteStreamSource.get_stream, and is a heler
1861
for RemoteStreamSource._get_stream to allow getting a stream
1862
reliably whether fallback back because of old servers or trying
1863
to stream from a non-RemoteRepository (which the stacked support
1866
source = repo._get_source(self.to_format)
1867
if isinstance(source, RemoteStreamSource):
1869
source = repo._real_repository._get_source(self.to_format)
1870
return source.get_stream(search)
1872
def _get_stream(self, repo, search):
1873
"""Core worker to get a stream from repo for search.
1875
This is used by both get_stream and the stacking support logic. It
1876
deliberately gets a stream for repo which does not need to be
1877
self.from_repository. In the event that repo is not Remote, or
1878
cannot do a smart stream, a fallback is made to the generic
1879
repository._get_stream() interface, via self._real_stream.
1881
In the event of stacking, streams from _get_stream will not
1882
contain all the data for search - this is normal (see get_stream).
1884
:param repo: A repository.
1885
:param search: A search.
1887
# Fallbacks may be non-smart
1888
if not isinstance(repo, RemoteRepository):
1889
return self._real_stream(repo, search)
1890
client = repo._client
1891
medium = client._medium
1892
path = repo.bzrdir._path_for_remote_call(client)
1893
search_bytes = repo._serialise_search_result(search)
1894
args = (path, self.to_format.network_name())
1896
('Repository.get_stream_1.19', (1, 19)),
1897
('Repository.get_stream', (1, 13))]
1899
for verb, version in candidate_verbs:
1900
if medium._is_remote_before(version):
1903
response = repo._call_with_body_bytes_expecting_body(
1904
verb, args, search_bytes)
1905
except errors.UnknownSmartMethod:
1906
medium._remember_remote_is_before(version)
1908
response_tuple, response_handler = response
1912
return self._real_stream(repo, search)
1913
if response_tuple[0] != 'ok':
1914
raise errors.UnexpectedSmartServerResponse(response_tuple)
1915
byte_stream = response_handler.read_streamed_body()
1916
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1917
if src_format.network_name() != repo._format.network_name():
1918
raise AssertionError(
1919
"Mismatched RemoteRepository and stream src %r, %r" % (
1920
src_format.network_name(), repo._format.network_name()))
1923
def missing_parents_chain(self, search, sources):
1924
"""Chain multiple streams together to handle stacking.
1926
:param search: The overall search to satisfy with streams.
1927
:param sources: A list of Repository objects to query.
1929
self.from_serialiser = self.from_repository._format._serializer
1930
self.seen_revs = set()
1931
self.referenced_revs = set()
1932
# If there are heads in the search, or the key count is > 0, we are not
1934
while not search.is_empty() and len(sources) > 1:
1935
source = sources.pop(0)
1936
stream = self._get_stream(source, search)
1937
for kind, substream in stream:
1938
if kind != 'revisions':
1939
yield kind, substream
1941
yield kind, self.missing_parents_rev_handler(substream)
1942
search = search.refine(self.seen_revs, self.referenced_revs)
1943
self.seen_revs = set()
1944
self.referenced_revs = set()
1945
if not search.is_empty():
1946
for kind, stream in self._get_stream(sources[0], search):
1949
def missing_parents_rev_handler(self, substream):
1950
for content in substream:
1951
revision_bytes = content.get_bytes_as('fulltext')
1952
revision = self.from_serialiser.read_revision_from_string(
1954
self.seen_revs.add(content.key[-1])
1955
self.referenced_revs.update(revision.parent_ids)
1959
class RemoteBranchLockableFiles(LockableFiles):
1960
"""A 'LockableFiles' implementation that talks to a smart server.
1962
This is not a public interface class.
1965
def __init__(self, bzrdir, _client):
1966
self.bzrdir = bzrdir
1967
self._client = _client
1968
self._need_find_modes = True
1969
LockableFiles.__init__(
1970
self, bzrdir.get_branch_transport(None),
1971
'lock', lockdir.LockDir)
1973
def _find_modes(self):
1974
# RemoteBranches don't let the client set the mode of control files.
1975
self._dir_mode = None
1976
self._file_mode = None
1979
class RemoteBranchFormat(branch.BranchFormat):
1981
def __init__(self, network_name=None):
1982
super(RemoteBranchFormat, self).__init__()
1983
self._matchingbzrdir = RemoteBzrDirFormat()
1984
self._matchingbzrdir.set_branch_format(self)
1985
self._custom_format = None
1986
self._network_name = network_name
1988
def __eq__(self, other):
1989
return (isinstance(other, RemoteBranchFormat) and
1990
self.__dict__ == other.__dict__)
1992
def _ensure_real(self):
1993
if self._custom_format is None:
1994
self._custom_format = branch.network_format_registry.get(
1997
def get_format_description(self):
1998
return 'Remote BZR Branch'
2000
def network_name(self):
2001
return self._network_name
2003
def open(self, a_bzrdir, ignore_fallbacks=False):
2004
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2006
def _vfs_initialize(self, a_bzrdir):
2007
# Initialisation when using a local bzrdir object, or a non-vfs init
2008
# method is not available on the server.
2009
# self._custom_format is always set - the start of initialize ensures
2011
if isinstance(a_bzrdir, RemoteBzrDir):
2012
a_bzrdir._ensure_real()
2013
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2015
# We assume the bzrdir is parameterised; it may not be.
2016
result = self._custom_format.initialize(a_bzrdir)
2017
if (isinstance(a_bzrdir, RemoteBzrDir) and
2018
not isinstance(result, RemoteBranch)):
2019
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2022
def initialize(self, a_bzrdir):
2023
# 1) get the network name to use.
2024
if self._custom_format:
2025
network_name = self._custom_format.network_name()
2027
# Select the current bzrlib default and ask for that.
2028
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2029
reference_format = reference_bzrdir_format.get_branch_format()
2030
self._custom_format = reference_format
2031
network_name = reference_format.network_name()
2032
# Being asked to create on a non RemoteBzrDir:
2033
if not isinstance(a_bzrdir, RemoteBzrDir):
2034
return self._vfs_initialize(a_bzrdir)
2035
medium = a_bzrdir._client._medium
2036
if medium._is_remote_before((1, 13)):
2037
return self._vfs_initialize(a_bzrdir)
2038
# Creating on a remote bzr dir.
2039
# 2) try direct creation via RPC
2040
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2041
verb = 'BzrDir.create_branch'
2043
response = a_bzrdir._call(verb, path, network_name)
2044
except errors.UnknownSmartMethod:
2045
# Fallback - use vfs methods
2046
medium._remember_remote_is_before((1, 13))
2047
return self._vfs_initialize(a_bzrdir)
2048
if response[0] != 'ok':
2049
raise errors.UnexpectedSmartServerResponse(response)
2050
# Turn the response into a RemoteRepository object.
2051
format = RemoteBranchFormat(network_name=response[1])
2052
repo_format = response_tuple_to_repo_format(response[3:])
2053
if response[2] == '':
2054
repo_bzrdir = a_bzrdir
2056
repo_bzrdir = RemoteBzrDir(
2057
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2059
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2060
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2061
format=format, setup_stacking=False)
2062
# XXX: We know this is a new branch, so it must have revno 0, revid
2063
# NULL_REVISION. Creating the branch locked would make this be unable
2064
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2065
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2066
return remote_branch
2068
def make_tags(self, branch):
2070
return self._custom_format.make_tags(branch)
2072
def supports_tags(self):
2073
# Remote branches might support tags, but we won't know until we
2074
# access the real remote branch.
2076
return self._custom_format.supports_tags()
2078
def supports_stacking(self):
2080
return self._custom_format.supports_stacking()
2082
def supports_set_append_revisions_only(self):
2084
return self._custom_format.supports_set_append_revisions_only()
2087
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2088
"""Branch stored on a server accessed by HPSS RPC.
2090
At the moment most operations are mapped down to simple file operations.
2093
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2094
_client=None, format=None, setup_stacking=True):
2095
"""Create a RemoteBranch instance.
2097
:param real_branch: An optional local implementation of the branch
2098
format, usually accessing the data via the VFS.
2099
:param _client: Private parameter for testing.
2100
:param format: A RemoteBranchFormat object, None to create one
2101
automatically. If supplied it should have a network_name already
2103
:param setup_stacking: If True make an RPC call to determine the
2104
stacked (or not) status of the branch. If False assume the branch
2107
# We intentionally don't call the parent class's __init__, because it
2108
# will try to assign to self.tags, which is a property in this subclass.
2109
# And the parent's __init__ doesn't do much anyway.
2110
self.bzrdir = remote_bzrdir
2111
if _client is not None:
2112
self._client = _client
2114
self._client = remote_bzrdir._client
2115
self.repository = remote_repository
2116
if real_branch is not None:
2117
self._real_branch = real_branch
2118
# Give the remote repository the matching real repo.
2119
real_repo = self._real_branch.repository
2120
if isinstance(real_repo, RemoteRepository):
2121
real_repo._ensure_real()
2122
real_repo = real_repo._real_repository
2123
self.repository._set_real_repository(real_repo)
2124
# Give the branch the remote repository to let fast-pathing happen.
2125
self._real_branch.repository = self.repository
2127
self._real_branch = None
2128
# Fill out expected attributes of branch for bzrlib API users.
2129
self._clear_cached_state()
2130
self.base = self.bzrdir.root_transport.base
2131
self._control_files = None
2132
self._lock_mode = None
2133
self._lock_token = None
2134
self._repo_lock_token = None
2135
self._lock_count = 0
2136
self._leave_lock = False
2137
# Setup a format: note that we cannot call _ensure_real until all the
2138
# attributes above are set: This code cannot be moved higher up in this
2141
self._format = RemoteBranchFormat()
2142
if real_branch is not None:
2143
self._format._network_name = \
2144
self._real_branch._format.network_name()
2146
self._format = format
2147
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2148
# branch.open_branch method.
2149
self._real_ignore_fallbacks = not setup_stacking
2150
if not self._format._network_name:
2151
# Did not get from open_branchV2 - old server.
2153
self._format._network_name = \
2154
self._real_branch._format.network_name()
2155
self.tags = self._format.make_tags(self)
2156
# The base class init is not called, so we duplicate this:
2157
hooks = branch.Branch.hooks['open']
2160
self._is_stacked = False
2162
self._setup_stacking()
2164
def _setup_stacking(self):
2165
# configure stacking into the remote repository, by reading it from
2168
fallback_url = self.get_stacked_on_url()
2169
except (errors.NotStacked, errors.UnstackableBranchFormat,
2170
errors.UnstackableRepositoryFormat), e:
2172
self._is_stacked = True
2173
self._activate_fallback_location(fallback_url)
2175
def _get_config(self):
2176
return RemoteBranchConfig(self)
2178
def _get_real_transport(self):
2179
# if we try vfs access, return the real branch's vfs transport
2181
return self._real_branch._transport
2183
_transport = property(_get_real_transport)
2186
return "%s(%s)" % (self.__class__.__name__, self.base)
2190
def _ensure_real(self):
2191
"""Ensure that there is a _real_branch set.
2193
Used before calls to self._real_branch.
2195
if self._real_branch is None:
2196
if not vfs.vfs_enabled():
2197
raise AssertionError('smart server vfs must be enabled '
2198
'to use vfs implementation')
2199
self.bzrdir._ensure_real()
2200
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2201
ignore_fallbacks=self._real_ignore_fallbacks)
2202
if self.repository._real_repository is None:
2203
# Give the remote repository the matching real repo.
2204
real_repo = self._real_branch.repository
2205
if isinstance(real_repo, RemoteRepository):
2206
real_repo._ensure_real()
2207
real_repo = real_repo._real_repository
2208
self.repository._set_real_repository(real_repo)
2209
# Give the real branch the remote repository to let fast-pathing
2211
self._real_branch.repository = self.repository
2212
if self._lock_mode == 'r':
2213
self._real_branch.lock_read()
2214
elif self._lock_mode == 'w':
2215
self._real_branch.lock_write(token=self._lock_token)
2217
def _translate_error(self, err, **context):
2218
self.repository._translate_error(err, branch=self, **context)
2220
def _clear_cached_state(self):
2221
super(RemoteBranch, self)._clear_cached_state()
2222
if self._real_branch is not None:
2223
self._real_branch._clear_cached_state()
2225
def _clear_cached_state_of_remote_branch_only(self):
2226
"""Like _clear_cached_state, but doesn't clear the cache of
2229
This is useful when falling back to calling a method of
2230
self._real_branch that changes state. In that case the underlying
2231
branch changes, so we need to invalidate this RemoteBranch's cache of
2232
it. However, there's no need to invalidate the _real_branch's cache
2233
too, in fact doing so might harm performance.
2235
super(RemoteBranch, self)._clear_cached_state()
2238
def control_files(self):
2239
# Defer actually creating RemoteBranchLockableFiles until its needed,
2240
# because it triggers an _ensure_real that we otherwise might not need.
2241
if self._control_files is None:
2242
self._control_files = RemoteBranchLockableFiles(
2243
self.bzrdir, self._client)
2244
return self._control_files
2246
def _get_checkout_format(self):
2248
return self._real_branch._get_checkout_format()
2250
def get_physical_lock_status(self):
2251
"""See Branch.get_physical_lock_status()."""
2252
# should be an API call to the server, as branches must be lockable.
2254
return self._real_branch.get_physical_lock_status()
2256
def get_stacked_on_url(self):
2257
"""Get the URL this branch is stacked against.
2259
:raises NotStacked: If the branch is not stacked.
2260
:raises UnstackableBranchFormat: If the branch does not support
2262
:raises UnstackableRepositoryFormat: If the repository does not support
2266
# there may not be a repository yet, so we can't use
2267
# self._translate_error, so we can't use self._call either.
2268
response = self._client.call('Branch.get_stacked_on_url',
2269
self._remote_path())
2270
except errors.ErrorFromSmartServer, err:
2271
# there may not be a repository yet, so we can't call through
2272
# its _translate_error
2273
_translate_error(err, branch=self)
2274
except errors.UnknownSmartMethod, err:
2276
return self._real_branch.get_stacked_on_url()
2277
if response[0] != 'ok':
2278
raise errors.UnexpectedSmartServerResponse(response)
2281
def set_stacked_on_url(self, url):
2282
branch.Branch.set_stacked_on_url(self, url)
2284
self._is_stacked = False
2286
self._is_stacked = True
2288
def _vfs_get_tags_bytes(self):
2290
return self._real_branch._get_tags_bytes()
2292
def _get_tags_bytes(self):
2293
medium = self._client._medium
2294
if medium._is_remote_before((1, 13)):
2295
return self._vfs_get_tags_bytes()
2297
response = self._call('Branch.get_tags_bytes', self._remote_path())
2298
except errors.UnknownSmartMethod:
2299
medium._remember_remote_is_before((1, 13))
2300
return self._vfs_get_tags_bytes()
2303
def _vfs_set_tags_bytes(self, bytes):
2305
return self._real_branch._set_tags_bytes(bytes)
2307
def _set_tags_bytes(self, bytes):
2308
medium = self._client._medium
2309
if medium._is_remote_before((1, 18)):
2310
self._vfs_set_tags_bytes(bytes)
2314
self._remote_path(), self._lock_token, self._repo_lock_token)
2315
response = self._call_with_body_bytes(
2316
'Branch.set_tags_bytes', args, bytes)
2317
except errors.UnknownSmartMethod:
2318
medium._remember_remote_is_before((1, 18))
2319
self._vfs_set_tags_bytes(bytes)
2321
def lock_read(self):
2322
self.repository.lock_read()
2323
if not self._lock_mode:
2324
self._note_lock('r')
2325
self._lock_mode = 'r'
2326
self._lock_count = 1
2327
if self._real_branch is not None:
2328
self._real_branch.lock_read()
2330
self._lock_count += 1
2332
def _remote_lock_write(self, token):
2334
branch_token = repo_token = ''
2336
branch_token = token
2337
repo_token = self.repository.lock_write()
2338
self.repository.unlock()
2339
err_context = {'token': token}
2340
response = self._call(
2341
'Branch.lock_write', self._remote_path(), branch_token,
2342
repo_token or '', **err_context)
2343
if response[0] != 'ok':
2344
raise errors.UnexpectedSmartServerResponse(response)
2345
ok, branch_token, repo_token = response
2346
return branch_token, repo_token
2348
def lock_write(self, token=None):
2349
if not self._lock_mode:
2350
self._note_lock('w')
2351
# Lock the branch and repo in one remote call.
2352
remote_tokens = self._remote_lock_write(token)
2353
self._lock_token, self._repo_lock_token = remote_tokens
2354
if not self._lock_token:
2355
raise SmartProtocolError('Remote server did not return a token!')
2356
# Tell the self.repository object that it is locked.
2357
self.repository.lock_write(
2358
self._repo_lock_token, _skip_rpc=True)
2360
if self._real_branch is not None:
2361
self._real_branch.lock_write(token=self._lock_token)
2362
if token is not None:
2363
self._leave_lock = True
2365
self._leave_lock = False
2366
self._lock_mode = 'w'
2367
self._lock_count = 1
2368
elif self._lock_mode == 'r':
2369
raise errors.ReadOnlyTransaction
2371
if token is not None:
2372
# A token was given to lock_write, and we're relocking, so
2373
# check that the given token actually matches the one we
2375
if token != self._lock_token:
2376
raise errors.TokenMismatch(token, self._lock_token)
2377
self._lock_count += 1
2378
# Re-lock the repository too.
2379
self.repository.lock_write(self._repo_lock_token)
2380
return self._lock_token or None
2382
def _unlock(self, branch_token, repo_token):
2383
err_context = {'token': str((branch_token, repo_token))}
2384
response = self._call(
2385
'Branch.unlock', self._remote_path(), branch_token,
2386
repo_token or '', **err_context)
2387
if response == ('ok',):
2389
raise errors.UnexpectedSmartServerResponse(response)
2391
@only_raises(errors.LockNotHeld, errors.LockBroken)
2394
self._lock_count -= 1
2395
if not self._lock_count:
2396
self._clear_cached_state()
2397
mode = self._lock_mode
2398
self._lock_mode = None
2399
if self._real_branch is not None:
2400
if (not self._leave_lock and mode == 'w' and
2401
self._repo_lock_token):
2402
# If this RemoteBranch will remove the physical lock
2403
# for the repository, make sure the _real_branch
2404
# doesn't do it first. (Because the _real_branch's
2405
# repository is set to be the RemoteRepository.)
2406
self._real_branch.repository.leave_lock_in_place()
2407
self._real_branch.unlock()
2409
# Only write-locked branched need to make a remote method
2410
# call to perform the unlock.
2412
if not self._lock_token:
2413
raise AssertionError('Locked, but no token!')
2414
branch_token = self._lock_token
2415
repo_token = self._repo_lock_token
2416
self._lock_token = None
2417
self._repo_lock_token = None
2418
if not self._leave_lock:
2419
self._unlock(branch_token, repo_token)
2421
self.repository.unlock()
2423
def break_lock(self):
2425
return self._real_branch.break_lock()
2427
def leave_lock_in_place(self):
2428
if not self._lock_token:
2429
raise NotImplementedError(self.leave_lock_in_place)
2430
self._leave_lock = True
2432
def dont_leave_lock_in_place(self):
2433
if not self._lock_token:
2434
raise NotImplementedError(self.dont_leave_lock_in_place)
2435
self._leave_lock = False
2438
def get_rev_id(self, revno, history=None):
2440
return _mod_revision.NULL_REVISION
2441
last_revision_info = self.last_revision_info()
2442
ok, result = self.repository.get_rev_id_for_revno(
2443
revno, last_revision_info)
2446
missing_parent = result[1]
2447
# Either the revision named by the server is missing, or its parent
2448
# is. Call get_parent_map to determine which, so that we report a
2450
parent_map = self.repository.get_parent_map([missing_parent])
2451
if missing_parent in parent_map:
2452
missing_parent = parent_map[missing_parent]
2453
raise errors.RevisionNotPresent(missing_parent, self.repository)
2455
def _last_revision_info(self):
2456
response = self._call('Branch.last_revision_info', self._remote_path())
2457
if response[0] != 'ok':
2458
raise SmartProtocolError('unexpected response code %s' % (response,))
2459
revno = int(response[1])
2460
last_revision = response[2]
2461
return (revno, last_revision)
2463
def _gen_revision_history(self):
2464
"""See Branch._gen_revision_history()."""
2465
if self._is_stacked:
2467
return self._real_branch._gen_revision_history()
2468
response_tuple, response_handler = self._call_expecting_body(
2469
'Branch.revision_history', self._remote_path())
2470
if response_tuple[0] != 'ok':
2471
raise errors.UnexpectedSmartServerResponse(response_tuple)
2472
result = response_handler.read_body_bytes().split('\x00')
2477
def _remote_path(self):
2478
return self.bzrdir._path_for_remote_call(self._client)
2480
def _set_last_revision_descendant(self, revision_id, other_branch,
2481
allow_diverged=False, allow_overwrite_descendant=False):
2482
# This performs additional work to meet the hook contract; while its
2483
# undesirable, we have to synthesise the revno to call the hook, and
2484
# not calling the hook is worse as it means changes can't be prevented.
2485
# Having calculated this though, we can't just call into
2486
# set_last_revision_info as a simple call, because there is a set_rh
2487
# hook that some folk may still be using.
2488
old_revno, old_revid = self.last_revision_info()
2489
history = self._lefthand_history(revision_id)
2490
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2491
err_context = {'other_branch': other_branch}
2492
response = self._call('Branch.set_last_revision_ex',
2493
self._remote_path(), self._lock_token, self._repo_lock_token,
2494
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2496
self._clear_cached_state()
2497
if len(response) != 3 and response[0] != 'ok':
2498
raise errors.UnexpectedSmartServerResponse(response)
2499
new_revno, new_revision_id = response[1:]
2500
self._last_revision_info_cache = new_revno, new_revision_id
2501
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2502
if self._real_branch is not None:
2503
cache = new_revno, new_revision_id
2504
self._real_branch._last_revision_info_cache = cache
2506
def _set_last_revision(self, revision_id):
2507
old_revno, old_revid = self.last_revision_info()
2508
# This performs additional work to meet the hook contract; while its
2509
# undesirable, we have to synthesise the revno to call the hook, and
2510
# not calling the hook is worse as it means changes can't be prevented.
2511
# Having calculated this though, we can't just call into
2512
# set_last_revision_info as a simple call, because there is a set_rh
2513
# hook that some folk may still be using.
2514
history = self._lefthand_history(revision_id)
2515
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2516
self._clear_cached_state()
2517
response = self._call('Branch.set_last_revision',
2518
self._remote_path(), self._lock_token, self._repo_lock_token,
2520
if response != ('ok',):
2521
raise errors.UnexpectedSmartServerResponse(response)
2522
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2525
def set_revision_history(self, rev_history):
2526
# Send just the tip revision of the history; the server will generate
2527
# the full history from that. If the revision doesn't exist in this
2528
# branch, NoSuchRevision will be raised.
2529
if rev_history == []:
2532
rev_id = rev_history[-1]
2533
self._set_last_revision(rev_id)
2534
for hook in branch.Branch.hooks['set_rh']:
2535
hook(self, rev_history)
2536
self._cache_revision_history(rev_history)
2538
def _get_parent_location(self):
2539
medium = self._client._medium
2540
if medium._is_remote_before((1, 13)):
2541
return self._vfs_get_parent_location()
2543
response = self._call('Branch.get_parent', self._remote_path())
2544
except errors.UnknownSmartMethod:
2545
medium._remember_remote_is_before((1, 13))
2546
return self._vfs_get_parent_location()
2547
if len(response) != 1:
2548
raise errors.UnexpectedSmartServerResponse(response)
2549
parent_location = response[0]
2550
if parent_location == '':
2552
return parent_location
2554
def _vfs_get_parent_location(self):
2556
return self._real_branch._get_parent_location()
2558
def _set_parent_location(self, url):
2559
medium = self._client._medium
2560
if medium._is_remote_before((1, 15)):
2561
return self._vfs_set_parent_location(url)
2563
call_url = url or ''
2564
if type(call_url) is not str:
2565
raise AssertionError('url must be a str or None (%s)' % url)
2566
response = self._call('Branch.set_parent_location',
2567
self._remote_path(), self._lock_token, self._repo_lock_token,
2569
except errors.UnknownSmartMethod:
2570
medium._remember_remote_is_before((1, 15))
2571
return self._vfs_set_parent_location(url)
2573
raise errors.UnexpectedSmartServerResponse(response)
2575
def _vfs_set_parent_location(self, url):
2577
return self._real_branch._set_parent_location(url)
2580
def pull(self, source, overwrite=False, stop_revision=None,
2582
self._clear_cached_state_of_remote_branch_only()
2584
return self._real_branch.pull(
2585
source, overwrite=overwrite, stop_revision=stop_revision,
2586
_override_hook_target=self, **kwargs)
2589
def push(self, target, overwrite=False, stop_revision=None):
2591
return self._real_branch.push(
2592
target, overwrite=overwrite, stop_revision=stop_revision,
2593
_override_hook_source_branch=self)
2595
def is_locked(self):
2596
return self._lock_count >= 1
2599
def revision_id_to_revno(self, revision_id):
2601
return self._real_branch.revision_id_to_revno(revision_id)
2604
def set_last_revision_info(self, revno, revision_id):
2605
# XXX: These should be returned by the set_last_revision_info verb
2606
old_revno, old_revid = self.last_revision_info()
2607
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2608
revision_id = ensure_null(revision_id)
2610
response = self._call('Branch.set_last_revision_info',
2611
self._remote_path(), self._lock_token, self._repo_lock_token,
2612
str(revno), revision_id)
2613
except errors.UnknownSmartMethod:
2615
self._clear_cached_state_of_remote_branch_only()
2616
self._real_branch.set_last_revision_info(revno, revision_id)
2617
self._last_revision_info_cache = revno, revision_id
2619
if response == ('ok',):
2620
self._clear_cached_state()
2621
self._last_revision_info_cache = revno, revision_id
2622
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2623
# Update the _real_branch's cache too.
2624
if self._real_branch is not None:
2625
cache = self._last_revision_info_cache
2626
self._real_branch._last_revision_info_cache = cache
2628
raise errors.UnexpectedSmartServerResponse(response)
2631
def generate_revision_history(self, revision_id, last_rev=None,
2633
medium = self._client._medium
2634
if not medium._is_remote_before((1, 6)):
2635
# Use a smart method for 1.6 and above servers
2637
self._set_last_revision_descendant(revision_id, other_branch,
2638
allow_diverged=True, allow_overwrite_descendant=True)
2640
except errors.UnknownSmartMethod:
2641
medium._remember_remote_is_before((1, 6))
2642
self._clear_cached_state_of_remote_branch_only()
2643
self.set_revision_history(self._lefthand_history(revision_id,
2644
last_rev=last_rev,other_branch=other_branch))
2646
def set_push_location(self, location):
2648
return self._real_branch.set_push_location(location)
2651
class RemoteConfig(object):
2652
"""A Config that reads and writes from smart verbs.
2654
It is a low-level object that considers config data to be name/value pairs
2655
that may be associated with a section. Assigning meaning to the these
2656
values is done at higher levels like bzrlib.config.TreeConfig.
2659
def get_option(self, name, section=None, default=None):
2660
"""Return the value associated with a named option.
2662
:param name: The name of the value
2663
:param section: The section the option is in (if any)
2664
:param default: The value to return if the value is not set
2665
:return: The value or default value
2668
configobj = self._get_configobj()
2670
section_obj = configobj
2673
section_obj = configobj[section]
2676
return section_obj.get(name, default)
2677
except errors.UnknownSmartMethod:
2678
return self._vfs_get_option(name, section, default)
2680
def _response_to_configobj(self, response):
2681
if len(response[0]) and response[0][0] != 'ok':
2682
raise errors.UnexpectedSmartServerResponse(response)
2683
lines = response[1].read_body_bytes().splitlines()
2684
return config.ConfigObj(lines, encoding='utf-8')
2687
class RemoteBranchConfig(RemoteConfig):
2688
"""A RemoteConfig for Branches."""
2690
def __init__(self, branch):
2691
self._branch = branch
2693
def _get_configobj(self):
2694
path = self._branch._remote_path()
2695
response = self._branch._client.call_expecting_body(
2696
'Branch.get_config_file', path)
2697
return self._response_to_configobj(response)
2699
def set_option(self, value, name, section=None):
2700
"""Set the value associated with a named option.
2702
:param value: The value to set
2703
:param name: The name of the value to set
2704
:param section: The section the option is in (if any)
2706
medium = self._branch._client._medium
2707
if medium._is_remote_before((1, 14)):
2708
return self._vfs_set_option(value, name, section)
2710
path = self._branch._remote_path()
2711
response = self._branch._client.call('Branch.set_config_option',
2712
path, self._branch._lock_token, self._branch._repo_lock_token,
2713
value.encode('utf8'), name, section or '')
2714
except errors.UnknownSmartMethod:
2715
medium._remember_remote_is_before((1, 14))
2716
return self._vfs_set_option(value, name, section)
2718
raise errors.UnexpectedSmartServerResponse(response)
2720
def _real_object(self):
2721
self._branch._ensure_real()
2722
return self._branch._real_branch
2724
def _vfs_set_option(self, value, name, section=None):
2725
return self._real_object()._get_config().set_option(
2726
value, name, section)
2729
class RemoteBzrDirConfig(RemoteConfig):
2730
"""A RemoteConfig for BzrDirs."""
2732
def __init__(self, bzrdir):
2733
self._bzrdir = bzrdir
2735
def _get_configobj(self):
2736
medium = self._bzrdir._client._medium
2737
verb = 'BzrDir.get_config_file'
2738
if medium._is_remote_before((1, 15)):
2739
raise errors.UnknownSmartMethod(verb)
2740
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2741
response = self._bzrdir._call_expecting_body(
2743
return self._response_to_configobj(response)
2745
def _vfs_get_option(self, name, section, default):
2746
return self._real_object()._get_config().get_option(
2747
name, section, default)
2749
def set_option(self, value, name, section=None):
2750
"""Set the value associated with a named option.
2752
:param value: The value to set
2753
:param name: The name of the value to set
2754
:param section: The section the option is in (if any)
2756
return self._real_object()._get_config().set_option(
2757
value, name, section)
2759
def _real_object(self):
2760
self._bzrdir._ensure_real()
2761
return self._bzrdir._real_bzrdir
2765
def _extract_tar(tar, to_dir):
2766
"""Extract all the contents of a tarfile object.
2768
A replacement for extractall, which is not present in python2.4
2771
tar.extract(tarinfo, to_dir)
2774
def _translate_error(err, **context):
2775
"""Translate an ErrorFromSmartServer into a more useful error.
2777
Possible context keys:
2785
If the error from the server doesn't match a known pattern, then
2786
UnknownErrorFromSmartServer is raised.
2790
return context[name]
2791
except KeyError, key_err:
2792
mutter('Missing key %r in context %r', key_err.args[0], context)
2795
"""Get the path from the context if present, otherwise use first error
2799
return context['path']
2800
except KeyError, key_err:
2802
return err.error_args[0]
2803
except IndexError, idx_err:
2805
'Missing key %r in context %r', key_err.args[0], context)
2808
if err.error_verb == 'IncompatibleRepositories':
2809
raise errors.IncompatibleRepositories(err.error_args[0],
2810
err.error_args[1], err.error_args[2])
2811
elif err.error_verb == 'NoSuchRevision':
2812
raise NoSuchRevision(find('branch'), err.error_args[0])
2813
elif err.error_verb == 'nosuchrevision':
2814
raise NoSuchRevision(find('repository'), err.error_args[0])
2815
elif err.error_tuple == ('nobranch',):
2816
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2817
elif err.error_verb == 'norepository':
2818
raise errors.NoRepositoryPresent(find('bzrdir'))
2819
elif err.error_verb == 'LockContention':
2820
raise errors.LockContention('(remote lock)')
2821
elif err.error_verb == 'UnlockableTransport':
2822
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2823
elif err.error_verb == 'LockFailed':
2824
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2825
elif err.error_verb == 'TokenMismatch':
2826
raise errors.TokenMismatch(find('token'), '(remote token)')
2827
elif err.error_verb == 'Diverged':
2828
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2829
elif err.error_verb == 'TipChangeRejected':
2830
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2831
elif err.error_verb == 'UnstackableBranchFormat':
2832
raise errors.UnstackableBranchFormat(*err.error_args)
2833
elif err.error_verb == 'UnstackableRepositoryFormat':
2834
raise errors.UnstackableRepositoryFormat(*err.error_args)
2835
elif err.error_verb == 'NotStacked':
2836
raise errors.NotStacked(branch=find('branch'))
2837
elif err.error_verb == 'PermissionDenied':
2839
if len(err.error_args) >= 2:
2840
extra = err.error_args[1]
2843
raise errors.PermissionDenied(path, extra=extra)
2844
elif err.error_verb == 'ReadError':
2846
raise errors.ReadError(path)
2847
elif err.error_verb == 'NoSuchFile':
2849
raise errors.NoSuchFile(path)
2850
elif err.error_verb == 'FileExists':
2851
raise errors.FileExists(err.error_args[0])
2852
elif err.error_verb == 'DirectoryNotEmpty':
2853
raise errors.DirectoryNotEmpty(err.error_args[0])
2854
elif err.error_verb == 'ShortReadvError':
2855
args = err.error_args
2856
raise errors.ShortReadvError(
2857
args[0], int(args[1]), int(args[2]), int(args[3]))
2858
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2859
encoding = str(err.error_args[0]) # encoding must always be a string
2860
val = err.error_args[1]
2861
start = int(err.error_args[2])
2862
end = int(err.error_args[3])
2863
reason = str(err.error_args[4]) # reason must always be a string
2864
if val.startswith('u:'):
2865
val = val[2:].decode('utf-8')
2866
elif val.startswith('s:'):
2867
val = val[2:].decode('base64')
2868
if err.error_verb == 'UnicodeDecodeError':
2869
raise UnicodeDecodeError(encoding, val, start, end, reason)
2870
elif err.error_verb == 'UnicodeEncodeError':
2871
raise UnicodeEncodeError(encoding, val, start, end, reason)
2872
elif err.error_verb == 'ReadOnlyError':
2873
raise errors.TransportNotPossible('readonly transport')
2874
raise errors.UnknownErrorFromSmartServer(err)