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
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)
1086
if not self._lock_count:
1087
return lock.cant_unlock_not_held(self)
1088
self._lock_count -= 1
1089
if self._lock_count > 0:
1091
self._unstacked_provider.disable_cache()
1092
old_mode = self._lock_mode
1093
self._lock_mode = None
1095
# The real repository is responsible at present for raising an
1096
# exception if it's in an unfinished write group. However, it
1097
# normally will *not* actually remove the lock from disk - that's
1098
# done by the server on receiving the Repository.unlock call.
1099
# This is just to let the _real_repository stay up to date.
1100
if self._real_repository is not None:
1101
self._real_repository.unlock()
1103
# The rpc-level lock should be released even if there was a
1104
# problem releasing the vfs-based lock.
1106
# Only write-locked repositories need to make a remote method
1107
# call to perform the unlock.
1108
old_token = self._lock_token
1109
self._lock_token = None
1110
if not self._leave_lock:
1111
self._unlock(old_token)
1112
# Fallbacks are always 'lock_read()' so we don't pay attention to
1114
for repo in self._fallback_repositories:
1117
def break_lock(self):
1118
# should hand off to the network
1120
return self._real_repository.break_lock()
1122
def _get_tarball(self, compression):
1123
"""Return a TemporaryFile containing a repository tarball.
1125
Returns None if the server does not support sending tarballs.
1128
path = self.bzrdir._path_for_remote_call(self._client)
1130
response, protocol = self._call_expecting_body(
1131
'Repository.tarball', path, compression)
1132
except errors.UnknownSmartMethod:
1133
protocol.cancel_read_body()
1135
if response[0] == 'ok':
1136
# Extract the tarball and return it
1137
t = tempfile.NamedTemporaryFile()
1138
# TODO: rpc layer should read directly into it...
1139
t.write(protocol.read_body_bytes())
1142
raise errors.UnexpectedSmartServerResponse(response)
1144
def sprout(self, to_bzrdir, revision_id=None):
1145
# TODO: Option to control what format is created?
1147
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1149
dest_repo.fetch(self, revision_id=revision_id)
1152
### These methods are just thin shims to the VFS object for now.
1154
def revision_tree(self, revision_id):
1156
return self._real_repository.revision_tree(revision_id)
1158
def get_serializer_format(self):
1160
return self._real_repository.get_serializer_format()
1162
def get_commit_builder(self, branch, parents, config, timestamp=None,
1163
timezone=None, committer=None, revprops=None,
1165
# FIXME: It ought to be possible to call this without immediately
1166
# triggering _ensure_real. For now it's the easiest thing to do.
1168
real_repo = self._real_repository
1169
builder = real_repo.get_commit_builder(branch, parents,
1170
config, timestamp=timestamp, timezone=timezone,
1171
committer=committer, revprops=revprops, revision_id=revision_id)
1174
def add_fallback_repository(self, repository):
1175
"""Add a repository to use for looking up data not held locally.
1177
:param repository: A repository.
1179
if not self._format.supports_external_lookups:
1180
raise errors.UnstackableRepositoryFormat(
1181
self._format.network_name(), self.base)
1182
# We need to accumulate additional repositories here, to pass them in
1185
if self.is_locked():
1186
# We will call fallback.unlock() when we transition to the unlocked
1187
# state, so always add a lock here. If a caller passes us a locked
1188
# repository, they are responsible for unlocking it later.
1189
repository.lock_read()
1190
self._fallback_repositories.append(repository)
1191
# If self._real_repository was parameterised already (e.g. because a
1192
# _real_branch had its get_stacked_on_url method called), then the
1193
# repository to be added may already be in the _real_repositories list.
1194
if self._real_repository is not None:
1195
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1196
self._real_repository._fallback_repositories]
1197
if repository.bzrdir.root_transport.base not in fallback_locations:
1198
self._real_repository.add_fallback_repository(repository)
1200
def add_inventory(self, revid, inv, parents):
1202
return self._real_repository.add_inventory(revid, inv, parents)
1204
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1207
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1208
delta, new_revision_id, parents)
1210
def add_revision(self, rev_id, rev, inv=None, config=None):
1212
return self._real_repository.add_revision(
1213
rev_id, rev, inv=inv, config=config)
1216
def get_inventory(self, revision_id):
1218
return self._real_repository.get_inventory(revision_id)
1220
def iter_inventories(self, revision_ids, ordering=None):
1222
return self._real_repository.iter_inventories(revision_ids, ordering)
1225
def get_revision(self, revision_id):
1227
return self._real_repository.get_revision(revision_id)
1229
def get_transaction(self):
1231
return self._real_repository.get_transaction()
1234
def clone(self, a_bzrdir, revision_id=None):
1236
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1238
def make_working_trees(self):
1239
"""See Repository.make_working_trees"""
1241
return self._real_repository.make_working_trees()
1243
def refresh_data(self):
1244
"""Re-read any data needed to to synchronise with disk.
1246
This method is intended to be called after another repository instance
1247
(such as one used by a smart server) has inserted data into the
1248
repository. It may not be called during a write group, but may be
1249
called at any other time.
1251
if self.is_in_write_group():
1252
raise errors.InternalBzrError(
1253
"May not refresh_data while in a write group.")
1254
if self._real_repository is not None:
1255
self._real_repository.refresh_data()
1257
def revision_ids_to_search_result(self, result_set):
1258
"""Convert a set of revision ids to a graph SearchResult."""
1259
result_parents = set()
1260
for parents in self.get_graph().get_parent_map(
1261
result_set).itervalues():
1262
result_parents.update(parents)
1263
included_keys = result_set.intersection(result_parents)
1264
start_keys = result_set.difference(included_keys)
1265
exclude_keys = result_parents.difference(result_set)
1266
result = graph.SearchResult(start_keys, exclude_keys,
1267
len(result_set), result_set)
1271
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1272
"""Return the revision ids that other has that this does not.
1274
These are returned in topological order.
1276
revision_id: only return revision ids included by revision_id.
1278
return repository.InterRepository.get(
1279
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1281
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1283
# No base implementation to use as RemoteRepository is not a subclass
1284
# of Repository; so this is a copy of Repository.fetch().
1285
if fetch_spec is not None and revision_id is not None:
1286
raise AssertionError(
1287
"fetch_spec and revision_id are mutually exclusive.")
1288
if self.is_in_write_group():
1289
raise errors.InternalBzrError(
1290
"May not fetch while in a write group.")
1291
# fast path same-url fetch operations
1292
if (self.has_same_location(source)
1293
and fetch_spec is None
1294
and self._has_same_fallbacks(source)):
1295
# check that last_revision is in 'from' and then return a
1297
if (revision_id is not None and
1298
not revision.is_null(revision_id)):
1299
self.get_revision(revision_id)
1301
# if there is no specific appropriate InterRepository, this will get
1302
# the InterRepository base class, which raises an
1303
# IncompatibleRepositories when asked to fetch.
1304
inter = repository.InterRepository.get(source, self)
1305
return inter.fetch(revision_id=revision_id, pb=pb,
1306
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1308
def create_bundle(self, target, base, fileobj, format=None):
1310
self._real_repository.create_bundle(target, base, fileobj, format)
1313
def get_ancestry(self, revision_id, topo_sorted=True):
1315
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1317
def fileids_altered_by_revision_ids(self, revision_ids):
1319
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1321
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1323
return self._real_repository._get_versioned_file_checker(
1324
revisions, revision_versions_cache)
1326
def iter_files_bytes(self, desired_files):
1327
"""See Repository.iter_file_bytes.
1330
return self._real_repository.iter_files_bytes(desired_files)
1332
def get_parent_map(self, revision_ids):
1333
"""See bzrlib.Graph.get_parent_map()."""
1334
return self._make_parents_provider().get_parent_map(revision_ids)
1336
def _get_parent_map_rpc(self, keys):
1337
"""Helper for get_parent_map that performs the RPC."""
1338
medium = self._client._medium
1339
if medium._is_remote_before((1, 2)):
1340
# We already found out that the server can't understand
1341
# Repository.get_parent_map requests, so just fetch the whole
1344
# Note that this reads the whole graph, when only some keys are
1345
# wanted. On this old server there's no way (?) to get them all
1346
# in one go, and the user probably will have seen a warning about
1347
# the server being old anyhow.
1348
rg = self._get_revision_graph(None)
1349
# There is an API discrepancy between get_parent_map and
1350
# get_revision_graph. Specifically, a "key:()" pair in
1351
# get_revision_graph just means a node has no parents. For
1352
# "get_parent_map" it means the node is a ghost. So fix up the
1353
# graph to correct this.
1354
# https://bugs.launchpad.net/bzr/+bug/214894
1355
# There is one other "bug" which is that ghosts in
1356
# get_revision_graph() are not returned at all. But we won't worry
1357
# about that for now.
1358
for node_id, parent_ids in rg.iteritems():
1359
if parent_ids == ():
1360
rg[node_id] = (NULL_REVISION,)
1361
rg[NULL_REVISION] = ()
1366
raise ValueError('get_parent_map(None) is not valid')
1367
if NULL_REVISION in keys:
1368
keys.discard(NULL_REVISION)
1369
found_parents = {NULL_REVISION:()}
1371
return found_parents
1374
# TODO(Needs analysis): We could assume that the keys being requested
1375
# from get_parent_map are in a breadth first search, so typically they
1376
# will all be depth N from some common parent, and we don't have to
1377
# have the server iterate from the root parent, but rather from the
1378
# keys we're searching; and just tell the server the keyspace we
1379
# already have; but this may be more traffic again.
1381
# Transform self._parents_map into a search request recipe.
1382
# TODO: Manage this incrementally to avoid covering the same path
1383
# repeatedly. (The server will have to on each request, but the less
1384
# work done the better).
1386
# Negative caching notes:
1387
# new server sends missing when a request including the revid
1388
# 'include-missing:' is present in the request.
1389
# missing keys are serialised as missing:X, and we then call
1390
# provider.note_missing(X) for-all X
1391
parents_map = self._unstacked_provider.get_cached_map()
1392
if parents_map is None:
1393
# Repository is not locked, so there's no cache.
1395
# start_set is all the keys in the cache
1396
start_set = set(parents_map)
1397
# result set is all the references to keys in the cache
1398
result_parents = set()
1399
for parents in parents_map.itervalues():
1400
result_parents.update(parents)
1401
stop_keys = result_parents.difference(start_set)
1402
# We don't need to send ghosts back to the server as a position to
1404
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1405
key_count = len(parents_map)
1406
if (NULL_REVISION in result_parents
1407
and NULL_REVISION in self._unstacked_provider.missing_keys):
1408
# If we pruned NULL_REVISION from the stop_keys because it's also
1409
# in our cache of "missing" keys we need to increment our key count
1410
# by 1, because the reconsitituted SearchResult on the server will
1411
# still consider NULL_REVISION to be an included key.
1413
included_keys = start_set.intersection(result_parents)
1414
start_set.difference_update(included_keys)
1415
recipe = ('manual', start_set, stop_keys, key_count)
1416
body = self._serialise_search_recipe(recipe)
1417
path = self.bzrdir._path_for_remote_call(self._client)
1419
if type(key) is not str:
1421
"key %r not a plain string" % (key,))
1422
verb = 'Repository.get_parent_map'
1423
args = (path, 'include-missing:') + tuple(keys)
1425
response = self._call_with_body_bytes_expecting_body(
1427
except errors.UnknownSmartMethod:
1428
# Server does not support this method, so get the whole graph.
1429
# Worse, we have to force a disconnection, because the server now
1430
# doesn't realise it has a body on the wire to consume, so the
1431
# only way to recover is to abandon the connection.
1433
'Server is too old for fast get_parent_map, reconnecting. '
1434
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1436
# To avoid having to disconnect repeatedly, we keep track of the
1437
# fact the server doesn't understand remote methods added in 1.2.
1438
medium._remember_remote_is_before((1, 2))
1439
# Recurse just once and we should use the fallback code.
1440
return self._get_parent_map_rpc(keys)
1441
response_tuple, response_handler = response
1442
if response_tuple[0] not in ['ok']:
1443
response_handler.cancel_read_body()
1444
raise errors.UnexpectedSmartServerResponse(response_tuple)
1445
if response_tuple[0] == 'ok':
1446
coded = bz2.decompress(response_handler.read_body_bytes())
1448
# no revisions found
1450
lines = coded.split('\n')
1453
d = tuple(line.split())
1455
revision_graph[d[0]] = d[1:]
1458
if d[0].startswith('missing:'):
1460
self._unstacked_provider.note_missing_key(revid)
1462
# no parents - so give the Graph result
1464
revision_graph[d[0]] = (NULL_REVISION,)
1465
return revision_graph
1468
def get_signature_text(self, revision_id):
1470
return self._real_repository.get_signature_text(revision_id)
1473
def get_inventory_xml(self, revision_id):
1475
return self._real_repository.get_inventory_xml(revision_id)
1477
def deserialise_inventory(self, revision_id, xml):
1479
return self._real_repository.deserialise_inventory(revision_id, xml)
1481
def reconcile(self, other=None, thorough=False):
1483
return self._real_repository.reconcile(other=other, thorough=thorough)
1485
def all_revision_ids(self):
1487
return self._real_repository.all_revision_ids()
1490
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1492
return self._real_repository.get_deltas_for_revisions(revisions,
1493
specific_fileids=specific_fileids)
1496
def get_revision_delta(self, revision_id, specific_fileids=None):
1498
return self._real_repository.get_revision_delta(revision_id,
1499
specific_fileids=specific_fileids)
1502
def revision_trees(self, revision_ids):
1504
return self._real_repository.revision_trees(revision_ids)
1507
def get_revision_reconcile(self, revision_id):
1509
return self._real_repository.get_revision_reconcile(revision_id)
1512
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1514
return self._real_repository.check(revision_ids=revision_ids,
1515
callback_refs=callback_refs, check_repo=check_repo)
1517
def copy_content_into(self, destination, revision_id=None):
1519
return self._real_repository.copy_content_into(
1520
destination, revision_id=revision_id)
1522
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1523
# get a tarball of the remote repository, and copy from that into the
1525
from bzrlib import osutils
1527
# TODO: Maybe a progress bar while streaming the tarball?
1528
note("Copying repository content as tarball...")
1529
tar_file = self._get_tarball('bz2')
1530
if tar_file is None:
1532
destination = to_bzrdir.create_repository()
1534
tar = tarfile.open('repository', fileobj=tar_file,
1536
tmpdir = osutils.mkdtemp()
1538
_extract_tar(tar, tmpdir)
1539
tmp_bzrdir = BzrDir.open(tmpdir)
1540
tmp_repo = tmp_bzrdir.open_repository()
1541
tmp_repo.copy_content_into(destination, revision_id)
1543
osutils.rmtree(tmpdir)
1547
# TODO: Suggestion from john: using external tar is much faster than
1548
# python's tarfile library, but it may not work on windows.
1551
def inventories(self):
1552
"""Decorate the real repository for now.
1554
In the long term a full blown network facility is needed to
1555
avoid creating a real repository object locally.
1558
return self._real_repository.inventories
1561
def pack(self, hint=None):
1562
"""Compress the data within the repository.
1564
This is not currently implemented within the smart server.
1567
return self._real_repository.pack(hint=hint)
1570
def revisions(self):
1571
"""Decorate the real repository for now.
1573
In the short term this should become a real object to intercept graph
1576
In the long term a full blown network facility is needed.
1579
return self._real_repository.revisions
1581
def set_make_working_trees(self, new_value):
1583
new_value_str = "True"
1585
new_value_str = "False"
1586
path = self.bzrdir._path_for_remote_call(self._client)
1588
response = self._call(
1589
'Repository.set_make_working_trees', path, new_value_str)
1590
except errors.UnknownSmartMethod:
1592
self._real_repository.set_make_working_trees(new_value)
1594
if response[0] != 'ok':
1595
raise errors.UnexpectedSmartServerResponse(response)
1598
def signatures(self):
1599
"""Decorate the real repository for now.
1601
In the long term a full blown network facility is needed to avoid
1602
creating a real repository object locally.
1605
return self._real_repository.signatures
1608
def sign_revision(self, revision_id, gpg_strategy):
1610
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1614
"""Decorate the real repository for now.
1616
In the long term a full blown network facility is needed to avoid
1617
creating a real repository object locally.
1620
return self._real_repository.texts
1623
def get_revisions(self, revision_ids):
1625
return self._real_repository.get_revisions(revision_ids)
1627
def supports_rich_root(self):
1628
return self._format.rich_root_data
1630
def iter_reverse_revision_history(self, revision_id):
1632
return self._real_repository.iter_reverse_revision_history(revision_id)
1635
def _serializer(self):
1636
return self._format._serializer
1638
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1640
return self._real_repository.store_revision_signature(
1641
gpg_strategy, plaintext, revision_id)
1643
def add_signature_text(self, revision_id, signature):
1645
return self._real_repository.add_signature_text(revision_id, signature)
1647
def has_signature_for_revision_id(self, revision_id):
1649
return self._real_repository.has_signature_for_revision_id(revision_id)
1651
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1653
return self._real_repository.item_keys_introduced_by(revision_ids,
1654
_files_pb=_files_pb)
1656
def revision_graph_can_have_wrong_parents(self):
1657
# The answer depends on the remote repo format.
1659
return self._real_repository.revision_graph_can_have_wrong_parents()
1661
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1663
return self._real_repository._find_inconsistent_revision_parents(
1666
def _check_for_inconsistent_revision_parents(self):
1668
return self._real_repository._check_for_inconsistent_revision_parents()
1670
def _make_parents_provider(self, other=None):
1671
providers = [self._unstacked_provider]
1672
if other is not None:
1673
providers.insert(0, other)
1674
providers.extend(r._make_parents_provider() for r in
1675
self._fallback_repositories)
1676
return graph.StackedParentsProvider(providers)
1678
def _serialise_search_recipe(self, recipe):
1679
"""Serialise a graph search recipe.
1681
:param recipe: A search recipe (start, stop, count).
1682
:return: Serialised bytes.
1684
start_keys = ' '.join(recipe[1])
1685
stop_keys = ' '.join(recipe[2])
1686
count = str(recipe[3])
1687
return '\n'.join((start_keys, stop_keys, count))
1689
def _serialise_search_result(self, search_result):
1690
if isinstance(search_result, graph.PendingAncestryResult):
1691
parts = ['ancestry-of']
1692
parts.extend(search_result.heads)
1694
recipe = search_result.get_recipe()
1695
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1696
return '\n'.join(parts)
1699
path = self.bzrdir._path_for_remote_call(self._client)
1701
response = self._call('PackRepository.autopack', path)
1702
except errors.UnknownSmartMethod:
1704
self._real_repository._pack_collection.autopack()
1707
if response[0] != 'ok':
1708
raise errors.UnexpectedSmartServerResponse(response)
1711
class RemoteStreamSink(repository.StreamSink):
1713
def _insert_real(self, stream, src_format, resume_tokens):
1714
self.target_repo._ensure_real()
1715
sink = self.target_repo._real_repository._get_sink()
1716
result = sink.insert_stream(stream, src_format, resume_tokens)
1718
self.target_repo.autopack()
1721
def insert_stream(self, stream, src_format, resume_tokens):
1722
target = self.target_repo
1723
target._unstacked_provider.missing_keys.clear()
1724
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1725
if target._lock_token:
1726
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1727
lock_args = (target._lock_token or '',)
1729
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1731
client = target._client
1732
medium = client._medium
1733
path = target.bzrdir._path_for_remote_call(client)
1734
# Probe for the verb to use with an empty stream before sending the
1735
# real stream to it. We do this both to avoid the risk of sending a
1736
# large request that is then rejected, and because we don't want to
1737
# implement a way to buffer, rewind, or restart the stream.
1739
for verb, required_version in candidate_calls:
1740
if medium._is_remote_before(required_version):
1743
# We've already done the probing (and set _is_remote_before) on
1744
# a previous insert.
1747
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1749
response = client.call_with_body_stream(
1750
(verb, path, '') + lock_args, byte_stream)
1751
except errors.UnknownSmartMethod:
1752
medium._remember_remote_is_before(required_version)
1758
return self._insert_real(stream, src_format, resume_tokens)
1759
self._last_inv_record = None
1760
self._last_substream = None
1761
if required_version < (1, 19):
1762
# Remote side doesn't support inventory deltas. Wrap the stream to
1763
# make sure we don't send any. If the stream contains inventory
1764
# deltas we'll interrupt the smart insert_stream request and
1766
stream = self._stop_stream_if_inventory_delta(stream)
1767
byte_stream = smart_repo._stream_to_byte_stream(
1769
resume_tokens = ' '.join(resume_tokens)
1770
response = client.call_with_body_stream(
1771
(verb, path, resume_tokens) + lock_args, byte_stream)
1772
if response[0][0] not in ('ok', 'missing-basis'):
1773
raise errors.UnexpectedSmartServerResponse(response)
1774
if self._last_substream is not None:
1775
# The stream included an inventory-delta record, but the remote
1776
# side isn't new enough to support them. So we need to send the
1777
# rest of the stream via VFS.
1778
self.target_repo.refresh_data()
1779
return self._resume_stream_with_vfs(response, src_format)
1780
if response[0][0] == 'missing-basis':
1781
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1782
resume_tokens = tokens
1783
return resume_tokens, set(missing_keys)
1785
self.target_repo.refresh_data()
1788
def _resume_stream_with_vfs(self, response, src_format):
1789
"""Resume sending a stream via VFS, first resending the record and
1790
substream that couldn't be sent via an insert_stream verb.
1792
if response[0][0] == 'missing-basis':
1793
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1794
# Ignore missing_keys, we haven't finished inserting yet
1797
def resume_substream():
1798
# Yield the substream that was interrupted.
1799
for record in self._last_substream:
1801
self._last_substream = None
1802
def resume_stream():
1803
# Finish sending the interrupted substream
1804
yield ('inventory-deltas', resume_substream())
1805
# Then simply continue sending the rest of the stream.
1806
for substream_kind, substream in self._last_stream:
1807
yield substream_kind, substream
1808
return self._insert_real(resume_stream(), src_format, tokens)
1810
def _stop_stream_if_inventory_delta(self, stream):
1811
"""Normally this just lets the original stream pass-through unchanged.
1813
However if any 'inventory-deltas' substream occurs it will stop
1814
streaming, and store the interrupted substream and stream in
1815
self._last_substream and self._last_stream so that the stream can be
1816
resumed by _resume_stream_with_vfs.
1819
stream_iter = iter(stream)
1820
for substream_kind, substream in stream_iter:
1821
if substream_kind == 'inventory-deltas':
1822
self._last_substream = substream
1823
self._last_stream = stream_iter
1826
yield substream_kind, substream
1829
class RemoteStreamSource(repository.StreamSource):
1830
"""Stream data from a remote server."""
1832
def get_stream(self, search):
1833
if (self.from_repository._fallback_repositories and
1834
self.to_format._fetch_order == 'topological'):
1835
return self._real_stream(self.from_repository, search)
1838
repos = [self.from_repository]
1844
repos.extend(repo._fallback_repositories)
1845
sources.append(repo)
1846
return self.missing_parents_chain(search, sources)
1848
def get_stream_for_missing_keys(self, missing_keys):
1849
self.from_repository._ensure_real()
1850
real_repo = self.from_repository._real_repository
1851
real_source = real_repo._get_source(self.to_format)
1852
return real_source.get_stream_for_missing_keys(missing_keys)
1854
def _real_stream(self, repo, search):
1855
"""Get a stream for search from repo.
1857
This never called RemoteStreamSource.get_stream, and is a heler
1858
for RemoteStreamSource._get_stream to allow getting a stream
1859
reliably whether fallback back because of old servers or trying
1860
to stream from a non-RemoteRepository (which the stacked support
1863
source = repo._get_source(self.to_format)
1864
if isinstance(source, RemoteStreamSource):
1866
source = repo._real_repository._get_source(self.to_format)
1867
return source.get_stream(search)
1869
def _get_stream(self, repo, search):
1870
"""Core worker to get a stream from repo for search.
1872
This is used by both get_stream and the stacking support logic. It
1873
deliberately gets a stream for repo which does not need to be
1874
self.from_repository. In the event that repo is not Remote, or
1875
cannot do a smart stream, a fallback is made to the generic
1876
repository._get_stream() interface, via self._real_stream.
1878
In the event of stacking, streams from _get_stream will not
1879
contain all the data for search - this is normal (see get_stream).
1881
:param repo: A repository.
1882
:param search: A search.
1884
# Fallbacks may be non-smart
1885
if not isinstance(repo, RemoteRepository):
1886
return self._real_stream(repo, search)
1887
client = repo._client
1888
medium = client._medium
1889
path = repo.bzrdir._path_for_remote_call(client)
1890
search_bytes = repo._serialise_search_result(search)
1891
args = (path, self.to_format.network_name())
1893
('Repository.get_stream_1.19', (1, 19)),
1894
('Repository.get_stream', (1, 13))]
1896
for verb, version in candidate_verbs:
1897
if medium._is_remote_before(version):
1900
response = repo._call_with_body_bytes_expecting_body(
1901
verb, args, search_bytes)
1902
except errors.UnknownSmartMethod:
1903
medium._remember_remote_is_before(version)
1905
response_tuple, response_handler = response
1909
return self._real_stream(repo, search)
1910
if response_tuple[0] != 'ok':
1911
raise errors.UnexpectedSmartServerResponse(response_tuple)
1912
byte_stream = response_handler.read_streamed_body()
1913
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1914
if src_format.network_name() != repo._format.network_name():
1915
raise AssertionError(
1916
"Mismatched RemoteRepository and stream src %r, %r" % (
1917
src_format.network_name(), repo._format.network_name()))
1920
def missing_parents_chain(self, search, sources):
1921
"""Chain multiple streams together to handle stacking.
1923
:param search: The overall search to satisfy with streams.
1924
:param sources: A list of Repository objects to query.
1926
self.serialiser = self.to_format._serializer
1927
self.seen_revs = set()
1928
self.referenced_revs = set()
1929
# If there are heads in the search, or the key count is > 0, we are not
1931
while not search.is_empty() and len(sources) > 1:
1932
source = sources.pop(0)
1933
stream = self._get_stream(source, search)
1934
for kind, substream in stream:
1935
if kind != 'revisions':
1936
yield kind, substream
1938
yield kind, self.missing_parents_rev_handler(substream)
1939
search = search.refine(self.seen_revs, self.referenced_revs)
1940
self.seen_revs = set()
1941
self.referenced_revs = set()
1942
if not search.is_empty():
1943
for kind, stream in self._get_stream(sources[0], search):
1946
def missing_parents_rev_handler(self, substream):
1947
for content in substream:
1948
revision_bytes = content.get_bytes_as('fulltext')
1949
revision = self.serialiser.read_revision_from_string(revision_bytes)
1950
self.seen_revs.add(content.key[-1])
1951
self.referenced_revs.update(revision.parent_ids)
1955
class RemoteBranchLockableFiles(LockableFiles):
1956
"""A 'LockableFiles' implementation that talks to a smart server.
1958
This is not a public interface class.
1961
def __init__(self, bzrdir, _client):
1962
self.bzrdir = bzrdir
1963
self._client = _client
1964
self._need_find_modes = True
1965
LockableFiles.__init__(
1966
self, bzrdir.get_branch_transport(None),
1967
'lock', lockdir.LockDir)
1969
def _find_modes(self):
1970
# RemoteBranches don't let the client set the mode of control files.
1971
self._dir_mode = None
1972
self._file_mode = None
1975
class RemoteBranchFormat(branch.BranchFormat):
1977
def __init__(self, network_name=None):
1978
super(RemoteBranchFormat, self).__init__()
1979
self._matchingbzrdir = RemoteBzrDirFormat()
1980
self._matchingbzrdir.set_branch_format(self)
1981
self._custom_format = None
1982
self._network_name = network_name
1984
def __eq__(self, other):
1985
return (isinstance(other, RemoteBranchFormat) and
1986
self.__dict__ == other.__dict__)
1988
def _ensure_real(self):
1989
if self._custom_format is None:
1990
self._custom_format = branch.network_format_registry.get(
1993
def get_format_description(self):
1994
return 'Remote BZR Branch'
1996
def network_name(self):
1997
return self._network_name
1999
def open(self, a_bzrdir, ignore_fallbacks=False):
2000
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2002
def _vfs_initialize(self, a_bzrdir):
2003
# Initialisation when using a local bzrdir object, or a non-vfs init
2004
# method is not available on the server.
2005
# self._custom_format is always set - the start of initialize ensures
2007
if isinstance(a_bzrdir, RemoteBzrDir):
2008
a_bzrdir._ensure_real()
2009
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2011
# We assume the bzrdir is parameterised; it may not be.
2012
result = self._custom_format.initialize(a_bzrdir)
2013
if (isinstance(a_bzrdir, RemoteBzrDir) and
2014
not isinstance(result, RemoteBranch)):
2015
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2018
def initialize(self, a_bzrdir):
2019
# 1) get the network name to use.
2020
if self._custom_format:
2021
network_name = self._custom_format.network_name()
2023
# Select the current bzrlib default and ask for that.
2024
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2025
reference_format = reference_bzrdir_format.get_branch_format()
2026
self._custom_format = reference_format
2027
network_name = reference_format.network_name()
2028
# Being asked to create on a non RemoteBzrDir:
2029
if not isinstance(a_bzrdir, RemoteBzrDir):
2030
return self._vfs_initialize(a_bzrdir)
2031
medium = a_bzrdir._client._medium
2032
if medium._is_remote_before((1, 13)):
2033
return self._vfs_initialize(a_bzrdir)
2034
# Creating on a remote bzr dir.
2035
# 2) try direct creation via RPC
2036
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2037
verb = 'BzrDir.create_branch'
2039
response = a_bzrdir._call(verb, path, network_name)
2040
except errors.UnknownSmartMethod:
2041
# Fallback - use vfs methods
2042
medium._remember_remote_is_before((1, 13))
2043
return self._vfs_initialize(a_bzrdir)
2044
if response[0] != 'ok':
2045
raise errors.UnexpectedSmartServerResponse(response)
2046
# Turn the response into a RemoteRepository object.
2047
format = RemoteBranchFormat(network_name=response[1])
2048
repo_format = response_tuple_to_repo_format(response[3:])
2049
if response[2] == '':
2050
repo_bzrdir = a_bzrdir
2052
repo_bzrdir = RemoteBzrDir(
2053
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2055
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2056
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2057
format=format, setup_stacking=False)
2058
# XXX: We know this is a new branch, so it must have revno 0, revid
2059
# NULL_REVISION. Creating the branch locked would make this be unable
2060
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2061
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2062
return remote_branch
2064
def make_tags(self, branch):
2066
return self._custom_format.make_tags(branch)
2068
def supports_tags(self):
2069
# Remote branches might support tags, but we won't know until we
2070
# access the real remote branch.
2072
return self._custom_format.supports_tags()
2074
def supports_stacking(self):
2076
return self._custom_format.supports_stacking()
2078
def supports_set_append_revisions_only(self):
2080
return self._custom_format.supports_set_append_revisions_only()
2083
class RemoteBranch(branch.Branch, _RpcHelper):
2084
"""Branch stored on a server accessed by HPSS RPC.
2086
At the moment most operations are mapped down to simple file operations.
2089
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2090
_client=None, format=None, setup_stacking=True):
2091
"""Create a RemoteBranch instance.
2093
:param real_branch: An optional local implementation of the branch
2094
format, usually accessing the data via the VFS.
2095
:param _client: Private parameter for testing.
2096
:param format: A RemoteBranchFormat object, None to create one
2097
automatically. If supplied it should have a network_name already
2099
:param setup_stacking: If True make an RPC call to determine the
2100
stacked (or not) status of the branch. If False assume the branch
2103
# We intentionally don't call the parent class's __init__, because it
2104
# will try to assign to self.tags, which is a property in this subclass.
2105
# And the parent's __init__ doesn't do much anyway.
2106
self.bzrdir = remote_bzrdir
2107
if _client is not None:
2108
self._client = _client
2110
self._client = remote_bzrdir._client
2111
self.repository = remote_repository
2112
if real_branch is not None:
2113
self._real_branch = real_branch
2114
# Give the remote repository the matching real repo.
2115
real_repo = self._real_branch.repository
2116
if isinstance(real_repo, RemoteRepository):
2117
real_repo._ensure_real()
2118
real_repo = real_repo._real_repository
2119
self.repository._set_real_repository(real_repo)
2120
# Give the branch the remote repository to let fast-pathing happen.
2121
self._real_branch.repository = self.repository
2123
self._real_branch = None
2124
# Fill out expected attributes of branch for bzrlib API users.
2125
self._clear_cached_state()
2126
self.base = self.bzrdir.root_transport.base
2127
self._control_files = None
2128
self._lock_mode = None
2129
self._lock_token = None
2130
self._repo_lock_token = None
2131
self._lock_count = 0
2132
self._leave_lock = False
2133
# Setup a format: note that we cannot call _ensure_real until all the
2134
# attributes above are set: This code cannot be moved higher up in this
2137
self._format = RemoteBranchFormat()
2138
if real_branch is not None:
2139
self._format._network_name = \
2140
self._real_branch._format.network_name()
2142
self._format = format
2143
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2144
# branch.open_branch method.
2145
self._real_ignore_fallbacks = not setup_stacking
2146
if not self._format._network_name:
2147
# Did not get from open_branchV2 - old server.
2149
self._format._network_name = \
2150
self._real_branch._format.network_name()
2151
self.tags = self._format.make_tags(self)
2152
# The base class init is not called, so we duplicate this:
2153
hooks = branch.Branch.hooks['open']
2156
self._is_stacked = False
2158
self._setup_stacking()
2160
def _setup_stacking(self):
2161
# configure stacking into the remote repository, by reading it from
2164
fallback_url = self.get_stacked_on_url()
2165
except (errors.NotStacked, errors.UnstackableBranchFormat,
2166
errors.UnstackableRepositoryFormat), e:
2168
self._is_stacked = True
2169
self._activate_fallback_location(fallback_url)
2171
def _get_config(self):
2172
return RemoteBranchConfig(self)
2174
def _get_real_transport(self):
2175
# if we try vfs access, return the real branch's vfs transport
2177
return self._real_branch._transport
2179
_transport = property(_get_real_transport)
2182
return "%s(%s)" % (self.__class__.__name__, self.base)
2186
def _ensure_real(self):
2187
"""Ensure that there is a _real_branch set.
2189
Used before calls to self._real_branch.
2191
if self._real_branch is None:
2192
if not vfs.vfs_enabled():
2193
raise AssertionError('smart server vfs must be enabled '
2194
'to use vfs implementation')
2195
self.bzrdir._ensure_real()
2196
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2197
ignore_fallbacks=self._real_ignore_fallbacks)
2198
if self.repository._real_repository is None:
2199
# Give the remote repository the matching real repo.
2200
real_repo = self._real_branch.repository
2201
if isinstance(real_repo, RemoteRepository):
2202
real_repo._ensure_real()
2203
real_repo = real_repo._real_repository
2204
self.repository._set_real_repository(real_repo)
2205
# Give the real branch the remote repository to let fast-pathing
2207
self._real_branch.repository = self.repository
2208
if self._lock_mode == 'r':
2209
self._real_branch.lock_read()
2210
elif self._lock_mode == 'w':
2211
self._real_branch.lock_write(token=self._lock_token)
2213
def _translate_error(self, err, **context):
2214
self.repository._translate_error(err, branch=self, **context)
2216
def _clear_cached_state(self):
2217
super(RemoteBranch, self)._clear_cached_state()
2218
if self._real_branch is not None:
2219
self._real_branch._clear_cached_state()
2221
def _clear_cached_state_of_remote_branch_only(self):
2222
"""Like _clear_cached_state, but doesn't clear the cache of
2225
This is useful when falling back to calling a method of
2226
self._real_branch that changes state. In that case the underlying
2227
branch changes, so we need to invalidate this RemoteBranch's cache of
2228
it. However, there's no need to invalidate the _real_branch's cache
2229
too, in fact doing so might harm performance.
2231
super(RemoteBranch, self)._clear_cached_state()
2234
def control_files(self):
2235
# Defer actually creating RemoteBranchLockableFiles until its needed,
2236
# because it triggers an _ensure_real that we otherwise might not need.
2237
if self._control_files is None:
2238
self._control_files = RemoteBranchLockableFiles(
2239
self.bzrdir, self._client)
2240
return self._control_files
2242
def _get_checkout_format(self):
2244
return self._real_branch._get_checkout_format()
2246
def get_physical_lock_status(self):
2247
"""See Branch.get_physical_lock_status()."""
2248
# should be an API call to the server, as branches must be lockable.
2250
return self._real_branch.get_physical_lock_status()
2252
def get_stacked_on_url(self):
2253
"""Get the URL this branch is stacked against.
2255
:raises NotStacked: If the branch is not stacked.
2256
:raises UnstackableBranchFormat: If the branch does not support
2258
:raises UnstackableRepositoryFormat: If the repository does not support
2262
# there may not be a repository yet, so we can't use
2263
# self._translate_error, so we can't use self._call either.
2264
response = self._client.call('Branch.get_stacked_on_url',
2265
self._remote_path())
2266
except errors.ErrorFromSmartServer, err:
2267
# there may not be a repository yet, so we can't call through
2268
# its _translate_error
2269
_translate_error(err, branch=self)
2270
except errors.UnknownSmartMethod, err:
2272
return self._real_branch.get_stacked_on_url()
2273
if response[0] != 'ok':
2274
raise errors.UnexpectedSmartServerResponse(response)
2277
def set_stacked_on_url(self, url):
2278
branch.Branch.set_stacked_on_url(self, url)
2280
self._is_stacked = False
2282
self._is_stacked = True
2284
def _vfs_get_tags_bytes(self):
2286
return self._real_branch._get_tags_bytes()
2288
def _get_tags_bytes(self):
2289
medium = self._client._medium
2290
if medium._is_remote_before((1, 13)):
2291
return self._vfs_get_tags_bytes()
2293
response = self._call('Branch.get_tags_bytes', self._remote_path())
2294
except errors.UnknownSmartMethod:
2295
medium._remember_remote_is_before((1, 13))
2296
return self._vfs_get_tags_bytes()
2299
def _vfs_set_tags_bytes(self, bytes):
2301
return self._real_branch._set_tags_bytes(bytes)
2303
def _set_tags_bytes(self, bytes):
2304
medium = self._client._medium
2305
if medium._is_remote_before((1, 18)):
2306
self._vfs_set_tags_bytes(bytes)
2310
self._remote_path(), self._lock_token, self._repo_lock_token)
2311
response = self._call_with_body_bytes(
2312
'Branch.set_tags_bytes', args, bytes)
2313
except errors.UnknownSmartMethod:
2314
medium._remember_remote_is_before((1, 18))
2315
self._vfs_set_tags_bytes(bytes)
2317
def lock_read(self):
2318
self.repository.lock_read()
2319
if not self._lock_mode:
2320
self._lock_mode = 'r'
2321
self._lock_count = 1
2322
if self._real_branch is not None:
2323
self._real_branch.lock_read()
2325
self._lock_count += 1
2327
def _remote_lock_write(self, token):
2329
branch_token = repo_token = ''
2331
branch_token = token
2332
repo_token = self.repository.lock_write()
2333
self.repository.unlock()
2334
err_context = {'token': token}
2335
response = self._call(
2336
'Branch.lock_write', self._remote_path(), branch_token,
2337
repo_token or '', **err_context)
2338
if response[0] != 'ok':
2339
raise errors.UnexpectedSmartServerResponse(response)
2340
ok, branch_token, repo_token = response
2341
return branch_token, repo_token
2343
def lock_write(self, token=None):
2344
if not self._lock_mode:
2345
# Lock the branch and repo in one remote call.
2346
remote_tokens = self._remote_lock_write(token)
2347
self._lock_token, self._repo_lock_token = remote_tokens
2348
if not self._lock_token:
2349
raise SmartProtocolError('Remote server did not return a token!')
2350
# Tell the self.repository object that it is locked.
2351
self.repository.lock_write(
2352
self._repo_lock_token, _skip_rpc=True)
2354
if self._real_branch is not None:
2355
self._real_branch.lock_write(token=self._lock_token)
2356
if token is not None:
2357
self._leave_lock = True
2359
self._leave_lock = False
2360
self._lock_mode = 'w'
2361
self._lock_count = 1
2362
elif self._lock_mode == 'r':
2363
raise errors.ReadOnlyTransaction
2365
if token is not None:
2366
# A token was given to lock_write, and we're relocking, so
2367
# check that the given token actually matches the one we
2369
if token != self._lock_token:
2370
raise errors.TokenMismatch(token, self._lock_token)
2371
self._lock_count += 1
2372
# Re-lock the repository too.
2373
self.repository.lock_write(self._repo_lock_token)
2374
return self._lock_token or None
2376
def _unlock(self, branch_token, repo_token):
2377
err_context = {'token': str((branch_token, repo_token))}
2378
response = self._call(
2379
'Branch.unlock', self._remote_path(), branch_token,
2380
repo_token or '', **err_context)
2381
if response == ('ok',):
2383
raise errors.UnexpectedSmartServerResponse(response)
2387
self._lock_count -= 1
2388
if not self._lock_count:
2389
self._clear_cached_state()
2390
mode = self._lock_mode
2391
self._lock_mode = None
2392
if self._real_branch is not None:
2393
if (not self._leave_lock and mode == 'w' and
2394
self._repo_lock_token):
2395
# If this RemoteBranch will remove the physical lock
2396
# for the repository, make sure the _real_branch
2397
# doesn't do it first. (Because the _real_branch's
2398
# repository is set to be the RemoteRepository.)
2399
self._real_branch.repository.leave_lock_in_place()
2400
self._real_branch.unlock()
2402
# Only write-locked branched need to make a remote method
2403
# call to perform the unlock.
2405
if not self._lock_token:
2406
raise AssertionError('Locked, but no token!')
2407
branch_token = self._lock_token
2408
repo_token = self._repo_lock_token
2409
self._lock_token = None
2410
self._repo_lock_token = None
2411
if not self._leave_lock:
2412
self._unlock(branch_token, repo_token)
2414
self.repository.unlock()
2416
def break_lock(self):
2418
return self._real_branch.break_lock()
2420
def leave_lock_in_place(self):
2421
if not self._lock_token:
2422
raise NotImplementedError(self.leave_lock_in_place)
2423
self._leave_lock = True
2425
def dont_leave_lock_in_place(self):
2426
if not self._lock_token:
2427
raise NotImplementedError(self.dont_leave_lock_in_place)
2428
self._leave_lock = False
2430
def get_rev_id(self, revno, history=None):
2432
return _mod_revision.NULL_REVISION
2433
last_revision_info = self.last_revision_info()
2434
ok, result = self.repository.get_rev_id_for_revno(
2435
revno, last_revision_info)
2438
missing_parent = result[1]
2439
# Either the revision named by the server is missing, or its parent
2440
# is. Call get_parent_map to determine which, so that we report a
2442
parent_map = self.repository.get_parent_map([missing_parent])
2443
if missing_parent in parent_map:
2444
missing_parent = parent_map[missing_parent]
2445
raise errors.RevisionNotPresent(missing_parent, self.repository)
2447
def _last_revision_info(self):
2448
response = self._call('Branch.last_revision_info', self._remote_path())
2449
if response[0] != 'ok':
2450
raise SmartProtocolError('unexpected response code %s' % (response,))
2451
revno = int(response[1])
2452
last_revision = response[2]
2453
return (revno, last_revision)
2455
def _gen_revision_history(self):
2456
"""See Branch._gen_revision_history()."""
2457
if self._is_stacked:
2459
return self._real_branch._gen_revision_history()
2460
response_tuple, response_handler = self._call_expecting_body(
2461
'Branch.revision_history', self._remote_path())
2462
if response_tuple[0] != 'ok':
2463
raise errors.UnexpectedSmartServerResponse(response_tuple)
2464
result = response_handler.read_body_bytes().split('\x00')
2469
def _remote_path(self):
2470
return self.bzrdir._path_for_remote_call(self._client)
2472
def _set_last_revision_descendant(self, revision_id, other_branch,
2473
allow_diverged=False, allow_overwrite_descendant=False):
2474
# This performs additional work to meet the hook contract; while its
2475
# undesirable, we have to synthesise the revno to call the hook, and
2476
# not calling the hook is worse as it means changes can't be prevented.
2477
# Having calculated this though, we can't just call into
2478
# set_last_revision_info as a simple call, because there is a set_rh
2479
# hook that some folk may still be using.
2480
old_revno, old_revid = self.last_revision_info()
2481
history = self._lefthand_history(revision_id)
2482
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2483
err_context = {'other_branch': other_branch}
2484
response = self._call('Branch.set_last_revision_ex',
2485
self._remote_path(), self._lock_token, self._repo_lock_token,
2486
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2488
self._clear_cached_state()
2489
if len(response) != 3 and response[0] != 'ok':
2490
raise errors.UnexpectedSmartServerResponse(response)
2491
new_revno, new_revision_id = response[1:]
2492
self._last_revision_info_cache = new_revno, new_revision_id
2493
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2494
if self._real_branch is not None:
2495
cache = new_revno, new_revision_id
2496
self._real_branch._last_revision_info_cache = cache
2498
def _set_last_revision(self, revision_id):
2499
old_revno, old_revid = self.last_revision_info()
2500
# This performs additional work to meet the hook contract; while its
2501
# undesirable, we have to synthesise the revno to call the hook, and
2502
# not calling the hook is worse as it means changes can't be prevented.
2503
# Having calculated this though, we can't just call into
2504
# set_last_revision_info as a simple call, because there is a set_rh
2505
# hook that some folk may still be using.
2506
history = self._lefthand_history(revision_id)
2507
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2508
self._clear_cached_state()
2509
response = self._call('Branch.set_last_revision',
2510
self._remote_path(), self._lock_token, self._repo_lock_token,
2512
if response != ('ok',):
2513
raise errors.UnexpectedSmartServerResponse(response)
2514
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2517
def set_revision_history(self, rev_history):
2518
# Send just the tip revision of the history; the server will generate
2519
# the full history from that. If the revision doesn't exist in this
2520
# branch, NoSuchRevision will be raised.
2521
if rev_history == []:
2524
rev_id = rev_history[-1]
2525
self._set_last_revision(rev_id)
2526
for hook in branch.Branch.hooks['set_rh']:
2527
hook(self, rev_history)
2528
self._cache_revision_history(rev_history)
2530
def _get_parent_location(self):
2531
medium = self._client._medium
2532
if medium._is_remote_before((1, 13)):
2533
return self._vfs_get_parent_location()
2535
response = self._call('Branch.get_parent', self._remote_path())
2536
except errors.UnknownSmartMethod:
2537
medium._remember_remote_is_before((1, 13))
2538
return self._vfs_get_parent_location()
2539
if len(response) != 1:
2540
raise errors.UnexpectedSmartServerResponse(response)
2541
parent_location = response[0]
2542
if parent_location == '':
2544
return parent_location
2546
def _vfs_get_parent_location(self):
2548
return self._real_branch._get_parent_location()
2550
def _set_parent_location(self, url):
2551
medium = self._client._medium
2552
if medium._is_remote_before((1, 15)):
2553
return self._vfs_set_parent_location(url)
2555
call_url = url or ''
2556
if type(call_url) is not str:
2557
raise AssertionError('url must be a str or None (%s)' % url)
2558
response = self._call('Branch.set_parent_location',
2559
self._remote_path(), self._lock_token, self._repo_lock_token,
2561
except errors.UnknownSmartMethod:
2562
medium._remember_remote_is_before((1, 15))
2563
return self._vfs_set_parent_location(url)
2565
raise errors.UnexpectedSmartServerResponse(response)
2567
def _vfs_set_parent_location(self, url):
2569
return self._real_branch._set_parent_location(url)
2572
def pull(self, source, overwrite=False, stop_revision=None,
2574
self._clear_cached_state_of_remote_branch_only()
2576
return self._real_branch.pull(
2577
source, overwrite=overwrite, stop_revision=stop_revision,
2578
_override_hook_target=self, **kwargs)
2581
def push(self, target, overwrite=False, stop_revision=None):
2583
return self._real_branch.push(
2584
target, overwrite=overwrite, stop_revision=stop_revision,
2585
_override_hook_source_branch=self)
2587
def is_locked(self):
2588
return self._lock_count >= 1
2591
def revision_id_to_revno(self, revision_id):
2593
return self._real_branch.revision_id_to_revno(revision_id)
2596
def set_last_revision_info(self, revno, revision_id):
2597
# XXX: These should be returned by the set_last_revision_info verb
2598
old_revno, old_revid = self.last_revision_info()
2599
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2600
revision_id = ensure_null(revision_id)
2602
response = self._call('Branch.set_last_revision_info',
2603
self._remote_path(), self._lock_token, self._repo_lock_token,
2604
str(revno), revision_id)
2605
except errors.UnknownSmartMethod:
2607
self._clear_cached_state_of_remote_branch_only()
2608
self._real_branch.set_last_revision_info(revno, revision_id)
2609
self._last_revision_info_cache = revno, revision_id
2611
if response == ('ok',):
2612
self._clear_cached_state()
2613
self._last_revision_info_cache = revno, revision_id
2614
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2615
# Update the _real_branch's cache too.
2616
if self._real_branch is not None:
2617
cache = self._last_revision_info_cache
2618
self._real_branch._last_revision_info_cache = cache
2620
raise errors.UnexpectedSmartServerResponse(response)
2623
def generate_revision_history(self, revision_id, last_rev=None,
2625
medium = self._client._medium
2626
if not medium._is_remote_before((1, 6)):
2627
# Use a smart method for 1.6 and above servers
2629
self._set_last_revision_descendant(revision_id, other_branch,
2630
allow_diverged=True, allow_overwrite_descendant=True)
2632
except errors.UnknownSmartMethod:
2633
medium._remember_remote_is_before((1, 6))
2634
self._clear_cached_state_of_remote_branch_only()
2635
self.set_revision_history(self._lefthand_history(revision_id,
2636
last_rev=last_rev,other_branch=other_branch))
2638
def set_push_location(self, location):
2640
return self._real_branch.set_push_location(location)
2643
class RemoteConfig(object):
2644
"""A Config that reads and writes from smart verbs.
2646
It is a low-level object that considers config data to be name/value pairs
2647
that may be associated with a section. Assigning meaning to the these
2648
values is done at higher levels like bzrlib.config.TreeConfig.
2651
def get_option(self, name, section=None, default=None):
2652
"""Return the value associated with a named option.
2654
:param name: The name of the value
2655
:param section: The section the option is in (if any)
2656
:param default: The value to return if the value is not set
2657
:return: The value or default value
2660
configobj = self._get_configobj()
2662
section_obj = configobj
2665
section_obj = configobj[section]
2668
return section_obj.get(name, default)
2669
except errors.UnknownSmartMethod:
2670
return self._vfs_get_option(name, section, default)
2672
def _response_to_configobj(self, response):
2673
if len(response[0]) and response[0][0] != 'ok':
2674
raise errors.UnexpectedSmartServerResponse(response)
2675
lines = response[1].read_body_bytes().splitlines()
2676
return config.ConfigObj(lines, encoding='utf-8')
2679
class RemoteBranchConfig(RemoteConfig):
2680
"""A RemoteConfig for Branches."""
2682
def __init__(self, branch):
2683
self._branch = branch
2685
def _get_configobj(self):
2686
path = self._branch._remote_path()
2687
response = self._branch._client.call_expecting_body(
2688
'Branch.get_config_file', path)
2689
return self._response_to_configobj(response)
2691
def set_option(self, value, name, section=None):
2692
"""Set the value associated with a named option.
2694
:param value: The value to set
2695
:param name: The name of the value to set
2696
:param section: The section the option is in (if any)
2698
medium = self._branch._client._medium
2699
if medium._is_remote_before((1, 14)):
2700
return self._vfs_set_option(value, name, section)
2702
path = self._branch._remote_path()
2703
response = self._branch._client.call('Branch.set_config_option',
2704
path, self._branch._lock_token, self._branch._repo_lock_token,
2705
value.encode('utf8'), name, section or '')
2706
except errors.UnknownSmartMethod:
2707
medium._remember_remote_is_before((1, 14))
2708
return self._vfs_set_option(value, name, section)
2710
raise errors.UnexpectedSmartServerResponse(response)
2712
def _real_object(self):
2713
self._branch._ensure_real()
2714
return self._branch._real_branch
2716
def _vfs_set_option(self, value, name, section=None):
2717
return self._real_object()._get_config().set_option(
2718
value, name, section)
2721
class RemoteBzrDirConfig(RemoteConfig):
2722
"""A RemoteConfig for BzrDirs."""
2724
def __init__(self, bzrdir):
2725
self._bzrdir = bzrdir
2727
def _get_configobj(self):
2728
medium = self._bzrdir._client._medium
2729
verb = 'BzrDir.get_config_file'
2730
if medium._is_remote_before((1, 15)):
2731
raise errors.UnknownSmartMethod(verb)
2732
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2733
response = self._bzrdir._call_expecting_body(
2735
return self._response_to_configobj(response)
2737
def _vfs_get_option(self, name, section, default):
2738
return self._real_object()._get_config().get_option(
2739
name, section, default)
2741
def set_option(self, value, name, section=None):
2742
"""Set the value associated with a named option.
2744
:param value: The value to set
2745
:param name: The name of the value to set
2746
:param section: The section the option is in (if any)
2748
return self._real_object()._get_config().set_option(
2749
value, name, section)
2751
def _real_object(self):
2752
self._bzrdir._ensure_real()
2753
return self._bzrdir._real_bzrdir
2757
def _extract_tar(tar, to_dir):
2758
"""Extract all the contents of a tarfile object.
2760
A replacement for extractall, which is not present in python2.4
2763
tar.extract(tarinfo, to_dir)
2766
def _translate_error(err, **context):
2767
"""Translate an ErrorFromSmartServer into a more useful error.
2769
Possible context keys:
2777
If the error from the server doesn't match a known pattern, then
2778
UnknownErrorFromSmartServer is raised.
2782
return context[name]
2783
except KeyError, key_err:
2784
mutter('Missing key %r in context %r', key_err.args[0], context)
2787
"""Get the path from the context if present, otherwise use first error
2791
return context['path']
2792
except KeyError, key_err:
2794
return err.error_args[0]
2795
except IndexError, idx_err:
2797
'Missing key %r in context %r', key_err.args[0], context)
2800
if err.error_verb == 'IncompatibleRepositories':
2801
raise errors.IncompatibleRepositories(err.error_args[0],
2802
err.error_args[1], err.error_args[2])
2803
elif err.error_verb == 'NoSuchRevision':
2804
raise NoSuchRevision(find('branch'), err.error_args[0])
2805
elif err.error_verb == 'nosuchrevision':
2806
raise NoSuchRevision(find('repository'), err.error_args[0])
2807
elif err.error_tuple == ('nobranch',):
2808
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2809
elif err.error_verb == 'norepository':
2810
raise errors.NoRepositoryPresent(find('bzrdir'))
2811
elif err.error_verb == 'LockContention':
2812
raise errors.LockContention('(remote lock)')
2813
elif err.error_verb == 'UnlockableTransport':
2814
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2815
elif err.error_verb == 'LockFailed':
2816
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2817
elif err.error_verb == 'TokenMismatch':
2818
raise errors.TokenMismatch(find('token'), '(remote token)')
2819
elif err.error_verb == 'Diverged':
2820
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2821
elif err.error_verb == 'TipChangeRejected':
2822
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2823
elif err.error_verb == 'UnstackableBranchFormat':
2824
raise errors.UnstackableBranchFormat(*err.error_args)
2825
elif err.error_verb == 'UnstackableRepositoryFormat':
2826
raise errors.UnstackableRepositoryFormat(*err.error_args)
2827
elif err.error_verb == 'NotStacked':
2828
raise errors.NotStacked(branch=find('branch'))
2829
elif err.error_verb == 'PermissionDenied':
2831
if len(err.error_args) >= 2:
2832
extra = err.error_args[1]
2835
raise errors.PermissionDenied(path, extra=extra)
2836
elif err.error_verb == 'ReadError':
2838
raise errors.ReadError(path)
2839
elif err.error_verb == 'NoSuchFile':
2841
raise errors.NoSuchFile(path)
2842
elif err.error_verb == 'FileExists':
2843
raise errors.FileExists(err.error_args[0])
2844
elif err.error_verb == 'DirectoryNotEmpty':
2845
raise errors.DirectoryNotEmpty(err.error_args[0])
2846
elif err.error_verb == 'ShortReadvError':
2847
args = err.error_args
2848
raise errors.ShortReadvError(
2849
args[0], int(args[1]), int(args[2]), int(args[3]))
2850
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2851
encoding = str(err.error_args[0]) # encoding must always be a string
2852
val = err.error_args[1]
2853
start = int(err.error_args[2])
2854
end = int(err.error_args[3])
2855
reason = str(err.error_args[4]) # reason must always be a string
2856
if val.startswith('u:'):
2857
val = val[2:].decode('utf-8')
2858
elif val.startswith('s:'):
2859
val = val[2:].decode('base64')
2860
if err.error_verb == 'UnicodeDecodeError':
2861
raise UnicodeDecodeError(encoding, val, start, end, reason)
2862
elif err.error_verb == 'UnicodeEncodeError':
2863
raise UnicodeEncodeError(encoding, val, start, end, reason)
2864
elif err.error_verb == 'ReadOnlyError':
2865
raise errors.TransportNotPossible('readonly transport')
2866
raise errors.UnknownErrorFromSmartServer(err)