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):
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:
952
self._lock_mode = 'r'
954
self._unstacked_provider.enable_cache(cache_misses=True)
955
if self._real_repository is not None:
956
self._real_repository.lock_read()
957
for repo in self._fallback_repositories:
960
self._lock_count += 1
962
def _remote_lock_write(self, token):
963
path = self.bzrdir._path_for_remote_call(self._client)
966
err_context = {'token': token}
967
response = self._call('Repository.lock_write', path, token,
969
if response[0] == 'ok':
973
raise errors.UnexpectedSmartServerResponse(response)
975
def lock_write(self, token=None, _skip_rpc=False):
976
if not self._lock_mode:
978
if self._lock_token is not None:
979
if token != self._lock_token:
980
raise errors.TokenMismatch(token, self._lock_token)
981
self._lock_token = token
983
self._lock_token = self._remote_lock_write(token)
984
# if self._lock_token is None, then this is something like packs or
985
# svn where we don't get to lock the repo, or a weave style repository
986
# where we cannot lock it over the wire and attempts to do so will
988
if self._real_repository is not None:
989
self._real_repository.lock_write(token=self._lock_token)
990
if token is not None:
991
self._leave_lock = True
993
self._leave_lock = False
994
self._lock_mode = 'w'
996
cache_misses = self._real_repository is None
997
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
998
for repo in self._fallback_repositories:
999
# Writes don't affect fallback repos
1001
elif self._lock_mode == 'r':
1002
raise errors.ReadOnlyError(self)
1004
self._lock_count += 1
1005
return self._lock_token or None
1007
def leave_lock_in_place(self):
1008
if not self._lock_token:
1009
raise NotImplementedError(self.leave_lock_in_place)
1010
self._leave_lock = True
1012
def dont_leave_lock_in_place(self):
1013
if not self._lock_token:
1014
raise NotImplementedError(self.dont_leave_lock_in_place)
1015
self._leave_lock = False
1017
def _set_real_repository(self, repository):
1018
"""Set the _real_repository for this repository.
1020
:param repository: The repository to fallback to for non-hpss
1021
implemented operations.
1023
if self._real_repository is not None:
1024
# Replacing an already set real repository.
1025
# We cannot do this [currently] if the repository is locked -
1026
# synchronised state might be lost.
1027
if self.is_locked():
1028
raise AssertionError('_real_repository is already set')
1029
if isinstance(repository, RemoteRepository):
1030
raise AssertionError()
1031
self._real_repository = repository
1032
# three code paths happen here:
1033
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1034
# up stacking. In this case self._fallback_repositories is [], and the
1035
# real repo is already setup. Preserve the real repo and
1036
# RemoteRepository.add_fallback_repository will avoid adding
1038
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1039
# ensure_real is triggered from a branch, the real repository to
1040
# set already has a matching list with separate instances, but
1041
# as they are also RemoteRepositories we don't worry about making the
1042
# lists be identical.
1043
# 3) new servers, RemoteRepository.ensure_real is triggered before
1044
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1045
# and need to populate it.
1046
if (self._fallback_repositories and
1047
len(self._real_repository._fallback_repositories) !=
1048
len(self._fallback_repositories)):
1049
if len(self._real_repository._fallback_repositories):
1050
raise AssertionError(
1051
"cannot cleanly remove existing _fallback_repositories")
1052
for fb in self._fallback_repositories:
1053
self._real_repository.add_fallback_repository(fb)
1054
if self._lock_mode == 'w':
1055
# if we are already locked, the real repository must be able to
1056
# acquire the lock with our token.
1057
self._real_repository.lock_write(self._lock_token)
1058
elif self._lock_mode == 'r':
1059
self._real_repository.lock_read()
1061
def start_write_group(self):
1062
"""Start a write group on the decorated repository.
1064
Smart methods perform operations in a single step so this API
1065
is not really applicable except as a compatibility thunk
1066
for older plugins that don't use e.g. the CommitBuilder
1070
return self._real_repository.start_write_group()
1072
def _unlock(self, token):
1073
path = self.bzrdir._path_for_remote_call(self._client)
1075
# with no token the remote repository is not persistently locked.
1077
err_context = {'token': token}
1078
response = self._call('Repository.unlock', path, token,
1080
if response == ('ok',):
1083
raise errors.UnexpectedSmartServerResponse(response)
1085
@only_raises(errors.LockNotHeld, errors.LockBroken)
1087
if not self._lock_count:
1088
return lock.cant_unlock_not_held(self)
1089
self._lock_count -= 1
1090
if self._lock_count > 0:
1092
self._unstacked_provider.disable_cache()
1093
old_mode = self._lock_mode
1094
self._lock_mode = None
1096
# The real repository is responsible at present for raising an
1097
# exception if it's in an unfinished write group. However, it
1098
# normally will *not* actually remove the lock from disk - that's
1099
# done by the server on receiving the Repository.unlock call.
1100
# This is just to let the _real_repository stay up to date.
1101
if self._real_repository is not None:
1102
self._real_repository.unlock()
1104
# The rpc-level lock should be released even if there was a
1105
# problem releasing the vfs-based lock.
1107
# Only write-locked repositories need to make a remote method
1108
# call to perform the unlock.
1109
old_token = self._lock_token
1110
self._lock_token = None
1111
if not self._leave_lock:
1112
self._unlock(old_token)
1113
# Fallbacks are always 'lock_read()' so we don't pay attention to
1115
for repo in self._fallback_repositories:
1118
def break_lock(self):
1119
# should hand off to the network
1121
return self._real_repository.break_lock()
1123
def _get_tarball(self, compression):
1124
"""Return a TemporaryFile containing a repository tarball.
1126
Returns None if the server does not support sending tarballs.
1129
path = self.bzrdir._path_for_remote_call(self._client)
1131
response, protocol = self._call_expecting_body(
1132
'Repository.tarball', path, compression)
1133
except errors.UnknownSmartMethod:
1134
protocol.cancel_read_body()
1136
if response[0] == 'ok':
1137
# Extract the tarball and return it
1138
t = tempfile.NamedTemporaryFile()
1139
# TODO: rpc layer should read directly into it...
1140
t.write(protocol.read_body_bytes())
1143
raise errors.UnexpectedSmartServerResponse(response)
1145
def sprout(self, to_bzrdir, revision_id=None):
1146
# TODO: Option to control what format is created?
1148
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1150
dest_repo.fetch(self, revision_id=revision_id)
1153
### These methods are just thin shims to the VFS object for now.
1155
def revision_tree(self, revision_id):
1157
return self._real_repository.revision_tree(revision_id)
1159
def get_serializer_format(self):
1161
return self._real_repository.get_serializer_format()
1163
def get_commit_builder(self, branch, parents, config, timestamp=None,
1164
timezone=None, committer=None, revprops=None,
1166
# FIXME: It ought to be possible to call this without immediately
1167
# triggering _ensure_real. For now it's the easiest thing to do.
1169
real_repo = self._real_repository
1170
builder = real_repo.get_commit_builder(branch, parents,
1171
config, timestamp=timestamp, timezone=timezone,
1172
committer=committer, revprops=revprops, revision_id=revision_id)
1175
def add_fallback_repository(self, repository):
1176
"""Add a repository to use for looking up data not held locally.
1178
:param repository: A repository.
1180
if not self._format.supports_external_lookups:
1181
raise errors.UnstackableRepositoryFormat(
1182
self._format.network_name(), self.base)
1183
# We need to accumulate additional repositories here, to pass them in
1186
if self.is_locked():
1187
# We will call fallback.unlock() when we transition to the unlocked
1188
# state, so always add a lock here. If a caller passes us a locked
1189
# repository, they are responsible for unlocking it later.
1190
repository.lock_read()
1191
self._fallback_repositories.append(repository)
1192
# If self._real_repository was parameterised already (e.g. because a
1193
# _real_branch had its get_stacked_on_url method called), then the
1194
# repository to be added may already be in the _real_repositories list.
1195
if self._real_repository is not None:
1196
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1197
self._real_repository._fallback_repositories]
1198
if repository.bzrdir.root_transport.base not in fallback_locations:
1199
self._real_repository.add_fallback_repository(repository)
1201
def add_inventory(self, revid, inv, parents):
1203
return self._real_repository.add_inventory(revid, inv, parents)
1205
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1208
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1209
delta, new_revision_id, parents)
1211
def add_revision(self, rev_id, rev, inv=None, config=None):
1213
return self._real_repository.add_revision(
1214
rev_id, rev, inv=inv, config=config)
1217
def get_inventory(self, revision_id):
1219
return self._real_repository.get_inventory(revision_id)
1221
def iter_inventories(self, revision_ids, ordering=None):
1223
return self._real_repository.iter_inventories(revision_ids, ordering)
1226
def get_revision(self, revision_id):
1228
return self._real_repository.get_revision(revision_id)
1230
def get_transaction(self):
1232
return self._real_repository.get_transaction()
1235
def clone(self, a_bzrdir, revision_id=None):
1237
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1239
def make_working_trees(self):
1240
"""See Repository.make_working_trees"""
1242
return self._real_repository.make_working_trees()
1244
def refresh_data(self):
1245
"""Re-read any data needed to to synchronise with disk.
1247
This method is intended to be called after another repository instance
1248
(such as one used by a smart server) has inserted data into the
1249
repository. It may not be called during a write group, but may be
1250
called at any other time.
1252
if self.is_in_write_group():
1253
raise errors.InternalBzrError(
1254
"May not refresh_data while in a write group.")
1255
if self._real_repository is not None:
1256
self._real_repository.refresh_data()
1258
def revision_ids_to_search_result(self, result_set):
1259
"""Convert a set of revision ids to a graph SearchResult."""
1260
result_parents = set()
1261
for parents in self.get_graph().get_parent_map(
1262
result_set).itervalues():
1263
result_parents.update(parents)
1264
included_keys = result_set.intersection(result_parents)
1265
start_keys = result_set.difference(included_keys)
1266
exclude_keys = result_parents.difference(result_set)
1267
result = graph.SearchResult(start_keys, exclude_keys,
1268
len(result_set), result_set)
1272
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1273
"""Return the revision ids that other has that this does not.
1275
These are returned in topological order.
1277
revision_id: only return revision ids included by revision_id.
1279
return repository.InterRepository.get(
1280
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1282
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1284
# No base implementation to use as RemoteRepository is not a subclass
1285
# of Repository; so this is a copy of Repository.fetch().
1286
if fetch_spec is not None and revision_id is not None:
1287
raise AssertionError(
1288
"fetch_spec and revision_id are mutually exclusive.")
1289
if self.is_in_write_group():
1290
raise errors.InternalBzrError(
1291
"May not fetch while in a write group.")
1292
# fast path same-url fetch operations
1293
if (self.has_same_location(source)
1294
and fetch_spec is None
1295
and self._has_same_fallbacks(source)):
1296
# check that last_revision is in 'from' and then return a
1298
if (revision_id is not None and
1299
not revision.is_null(revision_id)):
1300
self.get_revision(revision_id)
1302
# if there is no specific appropriate InterRepository, this will get
1303
# the InterRepository base class, which raises an
1304
# IncompatibleRepositories when asked to fetch.
1305
inter = repository.InterRepository.get(source, self)
1306
return inter.fetch(revision_id=revision_id, pb=pb,
1307
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1309
def create_bundle(self, target, base, fileobj, format=None):
1311
self._real_repository.create_bundle(target, base, fileobj, format)
1314
def get_ancestry(self, revision_id, topo_sorted=True):
1316
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1318
def fileids_altered_by_revision_ids(self, revision_ids):
1320
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1322
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1324
return self._real_repository._get_versioned_file_checker(
1325
revisions, revision_versions_cache)
1327
def iter_files_bytes(self, desired_files):
1328
"""See Repository.iter_file_bytes.
1331
return self._real_repository.iter_files_bytes(desired_files)
1333
def get_parent_map(self, revision_ids):
1334
"""See bzrlib.Graph.get_parent_map()."""
1335
return self._make_parents_provider().get_parent_map(revision_ids)
1337
def _get_parent_map_rpc(self, keys):
1338
"""Helper for get_parent_map that performs the RPC."""
1339
medium = self._client._medium
1340
if medium._is_remote_before((1, 2)):
1341
# We already found out that the server can't understand
1342
# Repository.get_parent_map requests, so just fetch the whole
1345
# Note that this reads the whole graph, when only some keys are
1346
# wanted. On this old server there's no way (?) to get them all
1347
# in one go, and the user probably will have seen a warning about
1348
# the server being old anyhow.
1349
rg = self._get_revision_graph(None)
1350
# There is an API discrepancy between get_parent_map and
1351
# get_revision_graph. Specifically, a "key:()" pair in
1352
# get_revision_graph just means a node has no parents. For
1353
# "get_parent_map" it means the node is a ghost. So fix up the
1354
# graph to correct this.
1355
# https://bugs.launchpad.net/bzr/+bug/214894
1356
# There is one other "bug" which is that ghosts in
1357
# get_revision_graph() are not returned at all. But we won't worry
1358
# about that for now.
1359
for node_id, parent_ids in rg.iteritems():
1360
if parent_ids == ():
1361
rg[node_id] = (NULL_REVISION,)
1362
rg[NULL_REVISION] = ()
1367
raise ValueError('get_parent_map(None) is not valid')
1368
if NULL_REVISION in keys:
1369
keys.discard(NULL_REVISION)
1370
found_parents = {NULL_REVISION:()}
1372
return found_parents
1375
# TODO(Needs analysis): We could assume that the keys being requested
1376
# from get_parent_map are in a breadth first search, so typically they
1377
# will all be depth N from some common parent, and we don't have to
1378
# have the server iterate from the root parent, but rather from the
1379
# keys we're searching; and just tell the server the keyspace we
1380
# already have; but this may be more traffic again.
1382
# Transform self._parents_map into a search request recipe.
1383
# TODO: Manage this incrementally to avoid covering the same path
1384
# repeatedly. (The server will have to on each request, but the less
1385
# work done the better).
1387
# Negative caching notes:
1388
# new server sends missing when a request including the revid
1389
# 'include-missing:' is present in the request.
1390
# missing keys are serialised as missing:X, and we then call
1391
# provider.note_missing(X) for-all X
1392
parents_map = self._unstacked_provider.get_cached_map()
1393
if parents_map is None:
1394
# Repository is not locked, so there's no cache.
1396
# start_set is all the keys in the cache
1397
start_set = set(parents_map)
1398
# result set is all the references to keys in the cache
1399
result_parents = set()
1400
for parents in parents_map.itervalues():
1401
result_parents.update(parents)
1402
stop_keys = result_parents.difference(start_set)
1403
# We don't need to send ghosts back to the server as a position to
1405
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1406
key_count = len(parents_map)
1407
if (NULL_REVISION in result_parents
1408
and NULL_REVISION in self._unstacked_provider.missing_keys):
1409
# If we pruned NULL_REVISION from the stop_keys because it's also
1410
# in our cache of "missing" keys we need to increment our key count
1411
# by 1, because the reconsitituted SearchResult on the server will
1412
# still consider NULL_REVISION to be an included key.
1414
included_keys = start_set.intersection(result_parents)
1415
start_set.difference_update(included_keys)
1416
recipe = ('manual', start_set, stop_keys, key_count)
1417
body = self._serialise_search_recipe(recipe)
1418
path = self.bzrdir._path_for_remote_call(self._client)
1420
if type(key) is not str:
1422
"key %r not a plain string" % (key,))
1423
verb = 'Repository.get_parent_map'
1424
args = (path, 'include-missing:') + tuple(keys)
1426
response = self._call_with_body_bytes_expecting_body(
1428
except errors.UnknownSmartMethod:
1429
# Server does not support this method, so get the whole graph.
1430
# Worse, we have to force a disconnection, because the server now
1431
# doesn't realise it has a body on the wire to consume, so the
1432
# only way to recover is to abandon the connection.
1434
'Server is too old for fast get_parent_map, reconnecting. '
1435
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1437
# To avoid having to disconnect repeatedly, we keep track of the
1438
# fact the server doesn't understand remote methods added in 1.2.
1439
medium._remember_remote_is_before((1, 2))
1440
# Recurse just once and we should use the fallback code.
1441
return self._get_parent_map_rpc(keys)
1442
response_tuple, response_handler = response
1443
if response_tuple[0] not in ['ok']:
1444
response_handler.cancel_read_body()
1445
raise errors.UnexpectedSmartServerResponse(response_tuple)
1446
if response_tuple[0] == 'ok':
1447
coded = bz2.decompress(response_handler.read_body_bytes())
1449
# no revisions found
1451
lines = coded.split('\n')
1454
d = tuple(line.split())
1456
revision_graph[d[0]] = d[1:]
1459
if d[0].startswith('missing:'):
1461
self._unstacked_provider.note_missing_key(revid)
1463
# no parents - so give the Graph result
1465
revision_graph[d[0]] = (NULL_REVISION,)
1466
return revision_graph
1469
def get_signature_text(self, revision_id):
1471
return self._real_repository.get_signature_text(revision_id)
1474
def get_inventory_xml(self, revision_id):
1476
return self._real_repository.get_inventory_xml(revision_id)
1478
def deserialise_inventory(self, revision_id, xml):
1480
return self._real_repository.deserialise_inventory(revision_id, xml)
1482
def reconcile(self, other=None, thorough=False):
1484
return self._real_repository.reconcile(other=other, thorough=thorough)
1486
def all_revision_ids(self):
1488
return self._real_repository.all_revision_ids()
1491
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1493
return self._real_repository.get_deltas_for_revisions(revisions,
1494
specific_fileids=specific_fileids)
1497
def get_revision_delta(self, revision_id, specific_fileids=None):
1499
return self._real_repository.get_revision_delta(revision_id,
1500
specific_fileids=specific_fileids)
1503
def revision_trees(self, revision_ids):
1505
return self._real_repository.revision_trees(revision_ids)
1508
def get_revision_reconcile(self, revision_id):
1510
return self._real_repository.get_revision_reconcile(revision_id)
1513
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1515
return self._real_repository.check(revision_ids=revision_ids,
1516
callback_refs=callback_refs, check_repo=check_repo)
1518
def copy_content_into(self, destination, revision_id=None):
1520
return self._real_repository.copy_content_into(
1521
destination, revision_id=revision_id)
1523
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1524
# get a tarball of the remote repository, and copy from that into the
1526
from bzrlib import osutils
1528
# TODO: Maybe a progress bar while streaming the tarball?
1529
note("Copying repository content as tarball...")
1530
tar_file = self._get_tarball('bz2')
1531
if tar_file is None:
1533
destination = to_bzrdir.create_repository()
1535
tar = tarfile.open('repository', fileobj=tar_file,
1537
tmpdir = osutils.mkdtemp()
1539
_extract_tar(tar, tmpdir)
1540
tmp_bzrdir = BzrDir.open(tmpdir)
1541
tmp_repo = tmp_bzrdir.open_repository()
1542
tmp_repo.copy_content_into(destination, revision_id)
1544
osutils.rmtree(tmpdir)
1548
# TODO: Suggestion from john: using external tar is much faster than
1549
# python's tarfile library, but it may not work on windows.
1552
def inventories(self):
1553
"""Decorate the real repository for now.
1555
In the long term a full blown network facility is needed to
1556
avoid creating a real repository object locally.
1559
return self._real_repository.inventories
1562
def pack(self, hint=None):
1563
"""Compress the data within the repository.
1565
This is not currently implemented within the smart server.
1568
return self._real_repository.pack(hint=hint)
1571
def revisions(self):
1572
"""Decorate the real repository for now.
1574
In the short term this should become a real object to intercept graph
1577
In the long term a full blown network facility is needed.
1580
return self._real_repository.revisions
1582
def set_make_working_trees(self, new_value):
1584
new_value_str = "True"
1586
new_value_str = "False"
1587
path = self.bzrdir._path_for_remote_call(self._client)
1589
response = self._call(
1590
'Repository.set_make_working_trees', path, new_value_str)
1591
except errors.UnknownSmartMethod:
1593
self._real_repository.set_make_working_trees(new_value)
1595
if response[0] != 'ok':
1596
raise errors.UnexpectedSmartServerResponse(response)
1599
def signatures(self):
1600
"""Decorate the real repository for now.
1602
In the long term a full blown network facility is needed to avoid
1603
creating a real repository object locally.
1606
return self._real_repository.signatures
1609
def sign_revision(self, revision_id, gpg_strategy):
1611
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1615
"""Decorate the real repository for now.
1617
In the long term a full blown network facility is needed to avoid
1618
creating a real repository object locally.
1621
return self._real_repository.texts
1624
def get_revisions(self, revision_ids):
1626
return self._real_repository.get_revisions(revision_ids)
1628
def supports_rich_root(self):
1629
return self._format.rich_root_data
1631
def iter_reverse_revision_history(self, revision_id):
1633
return self._real_repository.iter_reverse_revision_history(revision_id)
1636
def _serializer(self):
1637
return self._format._serializer
1639
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1641
return self._real_repository.store_revision_signature(
1642
gpg_strategy, plaintext, revision_id)
1644
def add_signature_text(self, revision_id, signature):
1646
return self._real_repository.add_signature_text(revision_id, signature)
1648
def has_signature_for_revision_id(self, revision_id):
1650
return self._real_repository.has_signature_for_revision_id(revision_id)
1652
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1654
return self._real_repository.item_keys_introduced_by(revision_ids,
1655
_files_pb=_files_pb)
1657
def revision_graph_can_have_wrong_parents(self):
1658
# The answer depends on the remote repo format.
1660
return self._real_repository.revision_graph_can_have_wrong_parents()
1662
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1664
return self._real_repository._find_inconsistent_revision_parents(
1667
def _check_for_inconsistent_revision_parents(self):
1669
return self._real_repository._check_for_inconsistent_revision_parents()
1671
def _make_parents_provider(self, other=None):
1672
providers = [self._unstacked_provider]
1673
if other is not None:
1674
providers.insert(0, other)
1675
providers.extend(r._make_parents_provider() for r in
1676
self._fallback_repositories)
1677
return graph.StackedParentsProvider(providers)
1679
def _serialise_search_recipe(self, recipe):
1680
"""Serialise a graph search recipe.
1682
:param recipe: A search recipe (start, stop, count).
1683
:return: Serialised bytes.
1685
start_keys = ' '.join(recipe[1])
1686
stop_keys = ' '.join(recipe[2])
1687
count = str(recipe[3])
1688
return '\n'.join((start_keys, stop_keys, count))
1690
def _serialise_search_result(self, search_result):
1691
if isinstance(search_result, graph.PendingAncestryResult):
1692
parts = ['ancestry-of']
1693
parts.extend(search_result.heads)
1695
recipe = search_result.get_recipe()
1696
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1697
return '\n'.join(parts)
1700
path = self.bzrdir._path_for_remote_call(self._client)
1702
response = self._call('PackRepository.autopack', path)
1703
except errors.UnknownSmartMethod:
1705
self._real_repository._pack_collection.autopack()
1708
if response[0] != 'ok':
1709
raise errors.UnexpectedSmartServerResponse(response)
1712
class RemoteStreamSink(repository.StreamSink):
1714
def _insert_real(self, stream, src_format, resume_tokens):
1715
self.target_repo._ensure_real()
1716
sink = self.target_repo._real_repository._get_sink()
1717
result = sink.insert_stream(stream, src_format, resume_tokens)
1719
self.target_repo.autopack()
1722
def insert_stream(self, stream, src_format, resume_tokens):
1723
target = self.target_repo
1724
target._unstacked_provider.missing_keys.clear()
1725
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1726
if target._lock_token:
1727
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1728
lock_args = (target._lock_token or '',)
1730
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1732
client = target._client
1733
medium = client._medium
1734
path = target.bzrdir._path_for_remote_call(client)
1735
# Probe for the verb to use with an empty stream before sending the
1736
# real stream to it. We do this both to avoid the risk of sending a
1737
# large request that is then rejected, and because we don't want to
1738
# implement a way to buffer, rewind, or restart the stream.
1740
for verb, required_version in candidate_calls:
1741
if medium._is_remote_before(required_version):
1744
# We've already done the probing (and set _is_remote_before) on
1745
# a previous insert.
1748
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1750
response = client.call_with_body_stream(
1751
(verb, path, '') + lock_args, byte_stream)
1752
except errors.UnknownSmartMethod:
1753
medium._remember_remote_is_before(required_version)
1759
return self._insert_real(stream, src_format, resume_tokens)
1760
self._last_inv_record = None
1761
self._last_substream = None
1762
if required_version < (1, 19):
1763
# Remote side doesn't support inventory deltas. Wrap the stream to
1764
# make sure we don't send any. If the stream contains inventory
1765
# deltas we'll interrupt the smart insert_stream request and
1767
stream = self._stop_stream_if_inventory_delta(stream)
1768
byte_stream = smart_repo._stream_to_byte_stream(
1770
resume_tokens = ' '.join(resume_tokens)
1771
response = client.call_with_body_stream(
1772
(verb, path, resume_tokens) + lock_args, byte_stream)
1773
if response[0][0] not in ('ok', 'missing-basis'):
1774
raise errors.UnexpectedSmartServerResponse(response)
1775
if self._last_substream is not None:
1776
# The stream included an inventory-delta record, but the remote
1777
# side isn't new enough to support them. So we need to send the
1778
# rest of the stream via VFS.
1779
self.target_repo.refresh_data()
1780
return self._resume_stream_with_vfs(response, src_format)
1781
if response[0][0] == 'missing-basis':
1782
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1783
resume_tokens = tokens
1784
return resume_tokens, set(missing_keys)
1786
self.target_repo.refresh_data()
1789
def _resume_stream_with_vfs(self, response, src_format):
1790
"""Resume sending a stream via VFS, first resending the record and
1791
substream that couldn't be sent via an insert_stream verb.
1793
if response[0][0] == 'missing-basis':
1794
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1795
# Ignore missing_keys, we haven't finished inserting yet
1798
def resume_substream():
1799
# Yield the substream that was interrupted.
1800
for record in self._last_substream:
1802
self._last_substream = None
1803
def resume_stream():
1804
# Finish sending the interrupted substream
1805
yield ('inventory-deltas', resume_substream())
1806
# Then simply continue sending the rest of the stream.
1807
for substream_kind, substream in self._last_stream:
1808
yield substream_kind, substream
1809
return self._insert_real(resume_stream(), src_format, tokens)
1811
def _stop_stream_if_inventory_delta(self, stream):
1812
"""Normally this just lets the original stream pass-through unchanged.
1814
However if any 'inventory-deltas' substream occurs it will stop
1815
streaming, and store the interrupted substream and stream in
1816
self._last_substream and self._last_stream so that the stream can be
1817
resumed by _resume_stream_with_vfs.
1820
stream_iter = iter(stream)
1821
for substream_kind, substream in stream_iter:
1822
if substream_kind == 'inventory-deltas':
1823
self._last_substream = substream
1824
self._last_stream = stream_iter
1827
yield substream_kind, substream
1830
class RemoteStreamSource(repository.StreamSource):
1831
"""Stream data from a remote server."""
1833
def get_stream(self, search):
1834
if (self.from_repository._fallback_repositories and
1835
self.to_format._fetch_order == 'topological'):
1836
return self._real_stream(self.from_repository, search)
1839
repos = [self.from_repository]
1845
repos.extend(repo._fallback_repositories)
1846
sources.append(repo)
1847
return self.missing_parents_chain(search, sources)
1849
def get_stream_for_missing_keys(self, missing_keys):
1850
self.from_repository._ensure_real()
1851
real_repo = self.from_repository._real_repository
1852
real_source = real_repo._get_source(self.to_format)
1853
return real_source.get_stream_for_missing_keys(missing_keys)
1855
def _real_stream(self, repo, search):
1856
"""Get a stream for search from repo.
1858
This never called RemoteStreamSource.get_stream, and is a heler
1859
for RemoteStreamSource._get_stream to allow getting a stream
1860
reliably whether fallback back because of old servers or trying
1861
to stream from a non-RemoteRepository (which the stacked support
1864
source = repo._get_source(self.to_format)
1865
if isinstance(source, RemoteStreamSource):
1867
source = repo._real_repository._get_source(self.to_format)
1868
return source.get_stream(search)
1870
def _get_stream(self, repo, search):
1871
"""Core worker to get a stream from repo for search.
1873
This is used by both get_stream and the stacking support logic. It
1874
deliberately gets a stream for repo which does not need to be
1875
self.from_repository. In the event that repo is not Remote, or
1876
cannot do a smart stream, a fallback is made to the generic
1877
repository._get_stream() interface, via self._real_stream.
1879
In the event of stacking, streams from _get_stream will not
1880
contain all the data for search - this is normal (see get_stream).
1882
:param repo: A repository.
1883
:param search: A search.
1885
# Fallbacks may be non-smart
1886
if not isinstance(repo, RemoteRepository):
1887
return self._real_stream(repo, search)
1888
client = repo._client
1889
medium = client._medium
1890
path = repo.bzrdir._path_for_remote_call(client)
1891
search_bytes = repo._serialise_search_result(search)
1892
args = (path, self.to_format.network_name())
1894
('Repository.get_stream_1.19', (1, 19)),
1895
('Repository.get_stream', (1, 13))]
1897
for verb, version in candidate_verbs:
1898
if medium._is_remote_before(version):
1901
response = repo._call_with_body_bytes_expecting_body(
1902
verb, args, search_bytes)
1903
except errors.UnknownSmartMethod:
1904
medium._remember_remote_is_before(version)
1906
response_tuple, response_handler = response
1910
return self._real_stream(repo, search)
1911
if response_tuple[0] != 'ok':
1912
raise errors.UnexpectedSmartServerResponse(response_tuple)
1913
byte_stream = response_handler.read_streamed_body()
1914
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1915
if src_format.network_name() != repo._format.network_name():
1916
raise AssertionError(
1917
"Mismatched RemoteRepository and stream src %r, %r" % (
1918
src_format.network_name(), repo._format.network_name()))
1921
def missing_parents_chain(self, search, sources):
1922
"""Chain multiple streams together to handle stacking.
1924
:param search: The overall search to satisfy with streams.
1925
:param sources: A list of Repository objects to query.
1927
self.from_serialiser = self.from_repository._format._serializer
1928
self.seen_revs = set()
1929
self.referenced_revs = set()
1930
# If there are heads in the search, or the key count is > 0, we are not
1932
while not search.is_empty() and len(sources) > 1:
1933
source = sources.pop(0)
1934
stream = self._get_stream(source, search)
1935
for kind, substream in stream:
1936
if kind != 'revisions':
1937
yield kind, substream
1939
yield kind, self.missing_parents_rev_handler(substream)
1940
search = search.refine(self.seen_revs, self.referenced_revs)
1941
self.seen_revs = set()
1942
self.referenced_revs = set()
1943
if not search.is_empty():
1944
for kind, stream in self._get_stream(sources[0], search):
1947
def missing_parents_rev_handler(self, substream):
1948
for content in substream:
1949
revision_bytes = content.get_bytes_as('fulltext')
1950
revision = self.from_serialiser.read_revision_from_string(
1952
self.seen_revs.add(content.key[-1])
1953
self.referenced_revs.update(revision.parent_ids)
1957
class RemoteBranchLockableFiles(LockableFiles):
1958
"""A 'LockableFiles' implementation that talks to a smart server.
1960
This is not a public interface class.
1963
def __init__(self, bzrdir, _client):
1964
self.bzrdir = bzrdir
1965
self._client = _client
1966
self._need_find_modes = True
1967
LockableFiles.__init__(
1968
self, bzrdir.get_branch_transport(None),
1969
'lock', lockdir.LockDir)
1971
def _find_modes(self):
1972
# RemoteBranches don't let the client set the mode of control files.
1973
self._dir_mode = None
1974
self._file_mode = None
1977
class RemoteBranchFormat(branch.BranchFormat):
1979
def __init__(self, network_name=None):
1980
super(RemoteBranchFormat, self).__init__()
1981
self._matchingbzrdir = RemoteBzrDirFormat()
1982
self._matchingbzrdir.set_branch_format(self)
1983
self._custom_format = None
1984
self._network_name = network_name
1986
def __eq__(self, other):
1987
return (isinstance(other, RemoteBranchFormat) and
1988
self.__dict__ == other.__dict__)
1990
def _ensure_real(self):
1991
if self._custom_format is None:
1992
self._custom_format = branch.network_format_registry.get(
1995
def get_format_description(self):
1996
return 'Remote BZR Branch'
1998
def network_name(self):
1999
return self._network_name
2001
def open(self, a_bzrdir, ignore_fallbacks=False):
2002
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2004
def _vfs_initialize(self, a_bzrdir):
2005
# Initialisation when using a local bzrdir object, or a non-vfs init
2006
# method is not available on the server.
2007
# self._custom_format is always set - the start of initialize ensures
2009
if isinstance(a_bzrdir, RemoteBzrDir):
2010
a_bzrdir._ensure_real()
2011
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2013
# We assume the bzrdir is parameterised; it may not be.
2014
result = self._custom_format.initialize(a_bzrdir)
2015
if (isinstance(a_bzrdir, RemoteBzrDir) and
2016
not isinstance(result, RemoteBranch)):
2017
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2020
def initialize(self, a_bzrdir):
2021
# 1) get the network name to use.
2022
if self._custom_format:
2023
network_name = self._custom_format.network_name()
2025
# Select the current bzrlib default and ask for that.
2026
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2027
reference_format = reference_bzrdir_format.get_branch_format()
2028
self._custom_format = reference_format
2029
network_name = reference_format.network_name()
2030
# Being asked to create on a non RemoteBzrDir:
2031
if not isinstance(a_bzrdir, RemoteBzrDir):
2032
return self._vfs_initialize(a_bzrdir)
2033
medium = a_bzrdir._client._medium
2034
if medium._is_remote_before((1, 13)):
2035
return self._vfs_initialize(a_bzrdir)
2036
# Creating on a remote bzr dir.
2037
# 2) try direct creation via RPC
2038
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2039
verb = 'BzrDir.create_branch'
2041
response = a_bzrdir._call(verb, path, network_name)
2042
except errors.UnknownSmartMethod:
2043
# Fallback - use vfs methods
2044
medium._remember_remote_is_before((1, 13))
2045
return self._vfs_initialize(a_bzrdir)
2046
if response[0] != 'ok':
2047
raise errors.UnexpectedSmartServerResponse(response)
2048
# Turn the response into a RemoteRepository object.
2049
format = RemoteBranchFormat(network_name=response[1])
2050
repo_format = response_tuple_to_repo_format(response[3:])
2051
if response[2] == '':
2052
repo_bzrdir = a_bzrdir
2054
repo_bzrdir = RemoteBzrDir(
2055
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2057
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2058
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2059
format=format, setup_stacking=False)
2060
# XXX: We know this is a new branch, so it must have revno 0, revid
2061
# NULL_REVISION. Creating the branch locked would make this be unable
2062
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2063
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2064
return remote_branch
2066
def make_tags(self, branch):
2068
return self._custom_format.make_tags(branch)
2070
def supports_tags(self):
2071
# Remote branches might support tags, but we won't know until we
2072
# access the real remote branch.
2074
return self._custom_format.supports_tags()
2076
def supports_stacking(self):
2078
return self._custom_format.supports_stacking()
2080
def supports_set_append_revisions_only(self):
2082
return self._custom_format.supports_set_append_revisions_only()
2085
class RemoteBranch(branch.Branch, _RpcHelper):
2086
"""Branch stored on a server accessed by HPSS RPC.
2088
At the moment most operations are mapped down to simple file operations.
2091
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2092
_client=None, format=None, setup_stacking=True):
2093
"""Create a RemoteBranch instance.
2095
:param real_branch: An optional local implementation of the branch
2096
format, usually accessing the data via the VFS.
2097
:param _client: Private parameter for testing.
2098
:param format: A RemoteBranchFormat object, None to create one
2099
automatically. If supplied it should have a network_name already
2101
:param setup_stacking: If True make an RPC call to determine the
2102
stacked (or not) status of the branch. If False assume the branch
2105
# We intentionally don't call the parent class's __init__, because it
2106
# will try to assign to self.tags, which is a property in this subclass.
2107
# And the parent's __init__ doesn't do much anyway.
2108
self.bzrdir = remote_bzrdir
2109
if _client is not None:
2110
self._client = _client
2112
self._client = remote_bzrdir._client
2113
self.repository = remote_repository
2114
if real_branch is not None:
2115
self._real_branch = real_branch
2116
# Give the remote repository the matching real repo.
2117
real_repo = self._real_branch.repository
2118
if isinstance(real_repo, RemoteRepository):
2119
real_repo._ensure_real()
2120
real_repo = real_repo._real_repository
2121
self.repository._set_real_repository(real_repo)
2122
# Give the branch the remote repository to let fast-pathing happen.
2123
self._real_branch.repository = self.repository
2125
self._real_branch = None
2126
# Fill out expected attributes of branch for bzrlib API users.
2127
self._clear_cached_state()
2128
self.base = self.bzrdir.root_transport.base
2129
self._control_files = None
2130
self._lock_mode = None
2131
self._lock_token = None
2132
self._repo_lock_token = None
2133
self._lock_count = 0
2134
self._leave_lock = False
2135
# Setup a format: note that we cannot call _ensure_real until all the
2136
# attributes above are set: This code cannot be moved higher up in this
2139
self._format = RemoteBranchFormat()
2140
if real_branch is not None:
2141
self._format._network_name = \
2142
self._real_branch._format.network_name()
2144
self._format = format
2145
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2146
# branch.open_branch method.
2147
self._real_ignore_fallbacks = not setup_stacking
2148
if not self._format._network_name:
2149
# Did not get from open_branchV2 - old server.
2151
self._format._network_name = \
2152
self._real_branch._format.network_name()
2153
self.tags = self._format.make_tags(self)
2154
# The base class init is not called, so we duplicate this:
2155
hooks = branch.Branch.hooks['open']
2158
self._is_stacked = False
2160
self._setup_stacking()
2162
def _setup_stacking(self):
2163
# configure stacking into the remote repository, by reading it from
2166
fallback_url = self.get_stacked_on_url()
2167
except (errors.NotStacked, errors.UnstackableBranchFormat,
2168
errors.UnstackableRepositoryFormat), e:
2170
self._is_stacked = True
2171
self._activate_fallback_location(fallback_url)
2173
def _get_config(self):
2174
return RemoteBranchConfig(self)
2176
def _get_real_transport(self):
2177
# if we try vfs access, return the real branch's vfs transport
2179
return self._real_branch._transport
2181
_transport = property(_get_real_transport)
2184
return "%s(%s)" % (self.__class__.__name__, self.base)
2188
def _ensure_real(self):
2189
"""Ensure that there is a _real_branch set.
2191
Used before calls to self._real_branch.
2193
if self._real_branch is None:
2194
if not vfs.vfs_enabled():
2195
raise AssertionError('smart server vfs must be enabled '
2196
'to use vfs implementation')
2197
self.bzrdir._ensure_real()
2198
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2199
ignore_fallbacks=self._real_ignore_fallbacks)
2200
if self.repository._real_repository is None:
2201
# Give the remote repository the matching real repo.
2202
real_repo = self._real_branch.repository
2203
if isinstance(real_repo, RemoteRepository):
2204
real_repo._ensure_real()
2205
real_repo = real_repo._real_repository
2206
self.repository._set_real_repository(real_repo)
2207
# Give the real branch the remote repository to let fast-pathing
2209
self._real_branch.repository = self.repository
2210
if self._lock_mode == 'r':
2211
self._real_branch.lock_read()
2212
elif self._lock_mode == 'w':
2213
self._real_branch.lock_write(token=self._lock_token)
2215
def _translate_error(self, err, **context):
2216
self.repository._translate_error(err, branch=self, **context)
2218
def _clear_cached_state(self):
2219
super(RemoteBranch, self)._clear_cached_state()
2220
if self._real_branch is not None:
2221
self._real_branch._clear_cached_state()
2223
def _clear_cached_state_of_remote_branch_only(self):
2224
"""Like _clear_cached_state, but doesn't clear the cache of
2227
This is useful when falling back to calling a method of
2228
self._real_branch that changes state. In that case the underlying
2229
branch changes, so we need to invalidate this RemoteBranch's cache of
2230
it. However, there's no need to invalidate the _real_branch's cache
2231
too, in fact doing so might harm performance.
2233
super(RemoteBranch, self)._clear_cached_state()
2236
def control_files(self):
2237
# Defer actually creating RemoteBranchLockableFiles until its needed,
2238
# because it triggers an _ensure_real that we otherwise might not need.
2239
if self._control_files is None:
2240
self._control_files = RemoteBranchLockableFiles(
2241
self.bzrdir, self._client)
2242
return self._control_files
2244
def _get_checkout_format(self):
2246
return self._real_branch._get_checkout_format()
2248
def get_physical_lock_status(self):
2249
"""See Branch.get_physical_lock_status()."""
2250
# should be an API call to the server, as branches must be lockable.
2252
return self._real_branch.get_physical_lock_status()
2254
def get_stacked_on_url(self):
2255
"""Get the URL this branch is stacked against.
2257
:raises NotStacked: If the branch is not stacked.
2258
:raises UnstackableBranchFormat: If the branch does not support
2260
:raises UnstackableRepositoryFormat: If the repository does not support
2264
# there may not be a repository yet, so we can't use
2265
# self._translate_error, so we can't use self._call either.
2266
response = self._client.call('Branch.get_stacked_on_url',
2267
self._remote_path())
2268
except errors.ErrorFromSmartServer, err:
2269
# there may not be a repository yet, so we can't call through
2270
# its _translate_error
2271
_translate_error(err, branch=self)
2272
except errors.UnknownSmartMethod, err:
2274
return self._real_branch.get_stacked_on_url()
2275
if response[0] != 'ok':
2276
raise errors.UnexpectedSmartServerResponse(response)
2279
def set_stacked_on_url(self, url):
2280
branch.Branch.set_stacked_on_url(self, url)
2282
self._is_stacked = False
2284
self._is_stacked = True
2286
def _vfs_get_tags_bytes(self):
2288
return self._real_branch._get_tags_bytes()
2290
def _get_tags_bytes(self):
2291
medium = self._client._medium
2292
if medium._is_remote_before((1, 13)):
2293
return self._vfs_get_tags_bytes()
2295
response = self._call('Branch.get_tags_bytes', self._remote_path())
2296
except errors.UnknownSmartMethod:
2297
medium._remember_remote_is_before((1, 13))
2298
return self._vfs_get_tags_bytes()
2301
def _vfs_set_tags_bytes(self, bytes):
2303
return self._real_branch._set_tags_bytes(bytes)
2305
def _set_tags_bytes(self, bytes):
2306
medium = self._client._medium
2307
if medium._is_remote_before((1, 18)):
2308
self._vfs_set_tags_bytes(bytes)
2312
self._remote_path(), self._lock_token, self._repo_lock_token)
2313
response = self._call_with_body_bytes(
2314
'Branch.set_tags_bytes', args, bytes)
2315
except errors.UnknownSmartMethod:
2316
medium._remember_remote_is_before((1, 18))
2317
self._vfs_set_tags_bytes(bytes)
2319
def lock_read(self):
2320
self.repository.lock_read()
2321
if not self._lock_mode:
2322
self._lock_mode = 'r'
2323
self._lock_count = 1
2324
if self._real_branch is not None:
2325
self._real_branch.lock_read()
2327
self._lock_count += 1
2329
def _remote_lock_write(self, token):
2331
branch_token = repo_token = ''
2333
branch_token = token
2334
repo_token = self.repository.lock_write()
2335
self.repository.unlock()
2336
err_context = {'token': token}
2337
response = self._call(
2338
'Branch.lock_write', self._remote_path(), branch_token,
2339
repo_token or '', **err_context)
2340
if response[0] != 'ok':
2341
raise errors.UnexpectedSmartServerResponse(response)
2342
ok, branch_token, repo_token = response
2343
return branch_token, repo_token
2345
def lock_write(self, token=None):
2346
if not self._lock_mode:
2347
# Lock the branch and repo in one remote call.
2348
remote_tokens = self._remote_lock_write(token)
2349
self._lock_token, self._repo_lock_token = remote_tokens
2350
if not self._lock_token:
2351
raise SmartProtocolError('Remote server did not return a token!')
2352
# Tell the self.repository object that it is locked.
2353
self.repository.lock_write(
2354
self._repo_lock_token, _skip_rpc=True)
2356
if self._real_branch is not None:
2357
self._real_branch.lock_write(token=self._lock_token)
2358
if token is not None:
2359
self._leave_lock = True
2361
self._leave_lock = False
2362
self._lock_mode = 'w'
2363
self._lock_count = 1
2364
elif self._lock_mode == 'r':
2365
raise errors.ReadOnlyTransaction
2367
if token is not None:
2368
# A token was given to lock_write, and we're relocking, so
2369
# check that the given token actually matches the one we
2371
if token != self._lock_token:
2372
raise errors.TokenMismatch(token, self._lock_token)
2373
self._lock_count += 1
2374
# Re-lock the repository too.
2375
self.repository.lock_write(self._repo_lock_token)
2376
return self._lock_token or None
2378
def _unlock(self, branch_token, repo_token):
2379
err_context = {'token': str((branch_token, repo_token))}
2380
response = self._call(
2381
'Branch.unlock', self._remote_path(), branch_token,
2382
repo_token or '', **err_context)
2383
if response == ('ok',):
2385
raise errors.UnexpectedSmartServerResponse(response)
2387
@only_raises(errors.LockNotHeld, errors.LockBroken)
2390
self._lock_count -= 1
2391
if not self._lock_count:
2392
self._clear_cached_state()
2393
mode = self._lock_mode
2394
self._lock_mode = None
2395
if self._real_branch is not None:
2396
if (not self._leave_lock and mode == 'w' and
2397
self._repo_lock_token):
2398
# If this RemoteBranch will remove the physical lock
2399
# for the repository, make sure the _real_branch
2400
# doesn't do it first. (Because the _real_branch's
2401
# repository is set to be the RemoteRepository.)
2402
self._real_branch.repository.leave_lock_in_place()
2403
self._real_branch.unlock()
2405
# Only write-locked branched need to make a remote method
2406
# call to perform the unlock.
2408
if not self._lock_token:
2409
raise AssertionError('Locked, but no token!')
2410
branch_token = self._lock_token
2411
repo_token = self._repo_lock_token
2412
self._lock_token = None
2413
self._repo_lock_token = None
2414
if not self._leave_lock:
2415
self._unlock(branch_token, repo_token)
2417
self.repository.unlock()
2419
def break_lock(self):
2421
return self._real_branch.break_lock()
2423
def leave_lock_in_place(self):
2424
if not self._lock_token:
2425
raise NotImplementedError(self.leave_lock_in_place)
2426
self._leave_lock = True
2428
def dont_leave_lock_in_place(self):
2429
if not self._lock_token:
2430
raise NotImplementedError(self.dont_leave_lock_in_place)
2431
self._leave_lock = False
2433
def get_rev_id(self, revno, history=None):
2435
return _mod_revision.NULL_REVISION
2436
last_revision_info = self.last_revision_info()
2437
ok, result = self.repository.get_rev_id_for_revno(
2438
revno, last_revision_info)
2441
missing_parent = result[1]
2442
# Either the revision named by the server is missing, or its parent
2443
# is. Call get_parent_map to determine which, so that we report a
2445
parent_map = self.repository.get_parent_map([missing_parent])
2446
if missing_parent in parent_map:
2447
missing_parent = parent_map[missing_parent]
2448
raise errors.RevisionNotPresent(missing_parent, self.repository)
2450
def _last_revision_info(self):
2451
response = self._call('Branch.last_revision_info', self._remote_path())
2452
if response[0] != 'ok':
2453
raise SmartProtocolError('unexpected response code %s' % (response,))
2454
revno = int(response[1])
2455
last_revision = response[2]
2456
return (revno, last_revision)
2458
def _gen_revision_history(self):
2459
"""See Branch._gen_revision_history()."""
2460
if self._is_stacked:
2462
return self._real_branch._gen_revision_history()
2463
response_tuple, response_handler = self._call_expecting_body(
2464
'Branch.revision_history', self._remote_path())
2465
if response_tuple[0] != 'ok':
2466
raise errors.UnexpectedSmartServerResponse(response_tuple)
2467
result = response_handler.read_body_bytes().split('\x00')
2472
def _remote_path(self):
2473
return self.bzrdir._path_for_remote_call(self._client)
2475
def _set_last_revision_descendant(self, revision_id, other_branch,
2476
allow_diverged=False, allow_overwrite_descendant=False):
2477
# This performs additional work to meet the hook contract; while its
2478
# undesirable, we have to synthesise the revno to call the hook, and
2479
# not calling the hook is worse as it means changes can't be prevented.
2480
# Having calculated this though, we can't just call into
2481
# set_last_revision_info as a simple call, because there is a set_rh
2482
# hook that some folk may still be using.
2483
old_revno, old_revid = self.last_revision_info()
2484
history = self._lefthand_history(revision_id)
2485
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2486
err_context = {'other_branch': other_branch}
2487
response = self._call('Branch.set_last_revision_ex',
2488
self._remote_path(), self._lock_token, self._repo_lock_token,
2489
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2491
self._clear_cached_state()
2492
if len(response) != 3 and response[0] != 'ok':
2493
raise errors.UnexpectedSmartServerResponse(response)
2494
new_revno, new_revision_id = response[1:]
2495
self._last_revision_info_cache = new_revno, new_revision_id
2496
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2497
if self._real_branch is not None:
2498
cache = new_revno, new_revision_id
2499
self._real_branch._last_revision_info_cache = cache
2501
def _set_last_revision(self, revision_id):
2502
old_revno, old_revid = self.last_revision_info()
2503
# This performs additional work to meet the hook contract; while its
2504
# undesirable, we have to synthesise the revno to call the hook, and
2505
# not calling the hook is worse as it means changes can't be prevented.
2506
# Having calculated this though, we can't just call into
2507
# set_last_revision_info as a simple call, because there is a set_rh
2508
# hook that some folk may still be using.
2509
history = self._lefthand_history(revision_id)
2510
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2511
self._clear_cached_state()
2512
response = self._call('Branch.set_last_revision',
2513
self._remote_path(), self._lock_token, self._repo_lock_token,
2515
if response != ('ok',):
2516
raise errors.UnexpectedSmartServerResponse(response)
2517
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2520
def set_revision_history(self, rev_history):
2521
# Send just the tip revision of the history; the server will generate
2522
# the full history from that. If the revision doesn't exist in this
2523
# branch, NoSuchRevision will be raised.
2524
if rev_history == []:
2527
rev_id = rev_history[-1]
2528
self._set_last_revision(rev_id)
2529
for hook in branch.Branch.hooks['set_rh']:
2530
hook(self, rev_history)
2531
self._cache_revision_history(rev_history)
2533
def _get_parent_location(self):
2534
medium = self._client._medium
2535
if medium._is_remote_before((1, 13)):
2536
return self._vfs_get_parent_location()
2538
response = self._call('Branch.get_parent', self._remote_path())
2539
except errors.UnknownSmartMethod:
2540
medium._remember_remote_is_before((1, 13))
2541
return self._vfs_get_parent_location()
2542
if len(response) != 1:
2543
raise errors.UnexpectedSmartServerResponse(response)
2544
parent_location = response[0]
2545
if parent_location == '':
2547
return parent_location
2549
def _vfs_get_parent_location(self):
2551
return self._real_branch._get_parent_location()
2553
def _set_parent_location(self, url):
2554
medium = self._client._medium
2555
if medium._is_remote_before((1, 15)):
2556
return self._vfs_set_parent_location(url)
2558
call_url = url or ''
2559
if type(call_url) is not str:
2560
raise AssertionError('url must be a str or None (%s)' % url)
2561
response = self._call('Branch.set_parent_location',
2562
self._remote_path(), self._lock_token, self._repo_lock_token,
2564
except errors.UnknownSmartMethod:
2565
medium._remember_remote_is_before((1, 15))
2566
return self._vfs_set_parent_location(url)
2568
raise errors.UnexpectedSmartServerResponse(response)
2570
def _vfs_set_parent_location(self, url):
2572
return self._real_branch._set_parent_location(url)
2575
def pull(self, source, overwrite=False, stop_revision=None,
2577
self._clear_cached_state_of_remote_branch_only()
2579
return self._real_branch.pull(
2580
source, overwrite=overwrite, stop_revision=stop_revision,
2581
_override_hook_target=self, **kwargs)
2584
def push(self, target, overwrite=False, stop_revision=None):
2586
return self._real_branch.push(
2587
target, overwrite=overwrite, stop_revision=stop_revision,
2588
_override_hook_source_branch=self)
2590
def is_locked(self):
2591
return self._lock_count >= 1
2594
def revision_id_to_revno(self, revision_id):
2596
return self._real_branch.revision_id_to_revno(revision_id)
2599
def set_last_revision_info(self, revno, revision_id):
2600
# XXX: These should be returned by the set_last_revision_info verb
2601
old_revno, old_revid = self.last_revision_info()
2602
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2603
revision_id = ensure_null(revision_id)
2605
response = self._call('Branch.set_last_revision_info',
2606
self._remote_path(), self._lock_token, self._repo_lock_token,
2607
str(revno), revision_id)
2608
except errors.UnknownSmartMethod:
2610
self._clear_cached_state_of_remote_branch_only()
2611
self._real_branch.set_last_revision_info(revno, revision_id)
2612
self._last_revision_info_cache = revno, revision_id
2614
if response == ('ok',):
2615
self._clear_cached_state()
2616
self._last_revision_info_cache = revno, revision_id
2617
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2618
# Update the _real_branch's cache too.
2619
if self._real_branch is not None:
2620
cache = self._last_revision_info_cache
2621
self._real_branch._last_revision_info_cache = cache
2623
raise errors.UnexpectedSmartServerResponse(response)
2626
def generate_revision_history(self, revision_id, last_rev=None,
2628
medium = self._client._medium
2629
if not medium._is_remote_before((1, 6)):
2630
# Use a smart method for 1.6 and above servers
2632
self._set_last_revision_descendant(revision_id, other_branch,
2633
allow_diverged=True, allow_overwrite_descendant=True)
2635
except errors.UnknownSmartMethod:
2636
medium._remember_remote_is_before((1, 6))
2637
self._clear_cached_state_of_remote_branch_only()
2638
self.set_revision_history(self._lefthand_history(revision_id,
2639
last_rev=last_rev,other_branch=other_branch))
2641
def set_push_location(self, location):
2643
return self._real_branch.set_push_location(location)
2646
class RemoteConfig(object):
2647
"""A Config that reads and writes from smart verbs.
2649
It is a low-level object that considers config data to be name/value pairs
2650
that may be associated with a section. Assigning meaning to the these
2651
values is done at higher levels like bzrlib.config.TreeConfig.
2654
def get_option(self, name, section=None, default=None):
2655
"""Return the value associated with a named option.
2657
:param name: The name of the value
2658
:param section: The section the option is in (if any)
2659
:param default: The value to return if the value is not set
2660
:return: The value or default value
2663
configobj = self._get_configobj()
2665
section_obj = configobj
2668
section_obj = configobj[section]
2671
return section_obj.get(name, default)
2672
except errors.UnknownSmartMethod:
2673
return self._vfs_get_option(name, section, default)
2675
def _response_to_configobj(self, response):
2676
if len(response[0]) and response[0][0] != 'ok':
2677
raise errors.UnexpectedSmartServerResponse(response)
2678
lines = response[1].read_body_bytes().splitlines()
2679
return config.ConfigObj(lines, encoding='utf-8')
2682
class RemoteBranchConfig(RemoteConfig):
2683
"""A RemoteConfig for Branches."""
2685
def __init__(self, branch):
2686
self._branch = branch
2688
def _get_configobj(self):
2689
path = self._branch._remote_path()
2690
response = self._branch._client.call_expecting_body(
2691
'Branch.get_config_file', path)
2692
return self._response_to_configobj(response)
2694
def set_option(self, value, name, section=None):
2695
"""Set the value associated with a named option.
2697
:param value: The value to set
2698
:param name: The name of the value to set
2699
:param section: The section the option is in (if any)
2701
medium = self._branch._client._medium
2702
if medium._is_remote_before((1, 14)):
2703
return self._vfs_set_option(value, name, section)
2705
path = self._branch._remote_path()
2706
response = self._branch._client.call('Branch.set_config_option',
2707
path, self._branch._lock_token, self._branch._repo_lock_token,
2708
value.encode('utf8'), name, section or '')
2709
except errors.UnknownSmartMethod:
2710
medium._remember_remote_is_before((1, 14))
2711
return self._vfs_set_option(value, name, section)
2713
raise errors.UnexpectedSmartServerResponse(response)
2715
def _real_object(self):
2716
self._branch._ensure_real()
2717
return self._branch._real_branch
2719
def _vfs_set_option(self, value, name, section=None):
2720
return self._real_object()._get_config().set_option(
2721
value, name, section)
2724
class RemoteBzrDirConfig(RemoteConfig):
2725
"""A RemoteConfig for BzrDirs."""
2727
def __init__(self, bzrdir):
2728
self._bzrdir = bzrdir
2730
def _get_configobj(self):
2731
medium = self._bzrdir._client._medium
2732
verb = 'BzrDir.get_config_file'
2733
if medium._is_remote_before((1, 15)):
2734
raise errors.UnknownSmartMethod(verb)
2735
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2736
response = self._bzrdir._call_expecting_body(
2738
return self._response_to_configobj(response)
2740
def _vfs_get_option(self, name, section, default):
2741
return self._real_object()._get_config().get_option(
2742
name, section, default)
2744
def set_option(self, value, name, section=None):
2745
"""Set the value associated with a named option.
2747
:param value: The value to set
2748
:param name: The name of the value to set
2749
:param section: The section the option is in (if any)
2751
return self._real_object()._get_config().set_option(
2752
value, name, section)
2754
def _real_object(self):
2755
self._bzrdir._ensure_real()
2756
return self._bzrdir._real_bzrdir
2760
def _extract_tar(tar, to_dir):
2761
"""Extract all the contents of a tarfile object.
2763
A replacement for extractall, which is not present in python2.4
2766
tar.extract(tarinfo, to_dir)
2769
def _translate_error(err, **context):
2770
"""Translate an ErrorFromSmartServer into a more useful error.
2772
Possible context keys:
2780
If the error from the server doesn't match a known pattern, then
2781
UnknownErrorFromSmartServer is raised.
2785
return context[name]
2786
except KeyError, key_err:
2787
mutter('Missing key %r in context %r', key_err.args[0], context)
2790
"""Get the path from the context if present, otherwise use first error
2794
return context['path']
2795
except KeyError, key_err:
2797
return err.error_args[0]
2798
except IndexError, idx_err:
2800
'Missing key %r in context %r', key_err.args[0], context)
2803
if err.error_verb == 'IncompatibleRepositories':
2804
raise errors.IncompatibleRepositories(err.error_args[0],
2805
err.error_args[1], err.error_args[2])
2806
elif err.error_verb == 'NoSuchRevision':
2807
raise NoSuchRevision(find('branch'), err.error_args[0])
2808
elif err.error_verb == 'nosuchrevision':
2809
raise NoSuchRevision(find('repository'), err.error_args[0])
2810
elif err.error_tuple == ('nobranch',):
2811
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2812
elif err.error_verb == 'norepository':
2813
raise errors.NoRepositoryPresent(find('bzrdir'))
2814
elif err.error_verb == 'LockContention':
2815
raise errors.LockContention('(remote lock)')
2816
elif err.error_verb == 'UnlockableTransport':
2817
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2818
elif err.error_verb == 'LockFailed':
2819
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2820
elif err.error_verb == 'TokenMismatch':
2821
raise errors.TokenMismatch(find('token'), '(remote token)')
2822
elif err.error_verb == 'Diverged':
2823
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2824
elif err.error_verb == 'TipChangeRejected':
2825
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2826
elif err.error_verb == 'UnstackableBranchFormat':
2827
raise errors.UnstackableBranchFormat(*err.error_args)
2828
elif err.error_verb == 'UnstackableRepositoryFormat':
2829
raise errors.UnstackableRepositoryFormat(*err.error_args)
2830
elif err.error_verb == 'NotStacked':
2831
raise errors.NotStacked(branch=find('branch'))
2832
elif err.error_verb == 'PermissionDenied':
2834
if len(err.error_args) >= 2:
2835
extra = err.error_args[1]
2838
raise errors.PermissionDenied(path, extra=extra)
2839
elif err.error_verb == 'ReadError':
2841
raise errors.ReadError(path)
2842
elif err.error_verb == 'NoSuchFile':
2844
raise errors.NoSuchFile(path)
2845
elif err.error_verb == 'FileExists':
2846
raise errors.FileExists(err.error_args[0])
2847
elif err.error_verb == 'DirectoryNotEmpty':
2848
raise errors.DirectoryNotEmpty(err.error_args[0])
2849
elif err.error_verb == 'ShortReadvError':
2850
args = err.error_args
2851
raise errors.ShortReadvError(
2852
args[0], int(args[1]), int(args[2]), int(args[3]))
2853
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2854
encoding = str(err.error_args[0]) # encoding must always be a string
2855
val = err.error_args[1]
2856
start = int(err.error_args[2])
2857
end = int(err.error_args[3])
2858
reason = str(err.error_args[4]) # reason must always be a string
2859
if val.startswith('u:'):
2860
val = val[2:].decode('utf-8')
2861
elif val.startswith('s:'):
2862
val = val[2:].decode('base64')
2863
if err.error_verb == 'UnicodeDecodeError':
2864
raise UnicodeDecodeError(encoding, val, start, end, reason)
2865
elif err.error_verb == 'UnicodeEncodeError':
2866
raise UnicodeEncodeError(encoding, val, start, end, reason)
2867
elif err.error_verb == 'ReadOnlyError':
2868
raise errors.TransportNotPossible('readonly transport')
2869
raise errors.UnknownErrorFromSmartServer(err)