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.from_serialiser = self.from_repository._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.from_serialiser.read_revision_from_string(
1951
self.seen_revs.add(content.key[-1])
1952
self.referenced_revs.update(revision.parent_ids)
1956
class RemoteBranchLockableFiles(LockableFiles):
1957
"""A 'LockableFiles' implementation that talks to a smart server.
1959
This is not a public interface class.
1962
def __init__(self, bzrdir, _client):
1963
self.bzrdir = bzrdir
1964
self._client = _client
1965
self._need_find_modes = True
1966
LockableFiles.__init__(
1967
self, bzrdir.get_branch_transport(None),
1968
'lock', lockdir.LockDir)
1970
def _find_modes(self):
1971
# RemoteBranches don't let the client set the mode of control files.
1972
self._dir_mode = None
1973
self._file_mode = None
1976
class RemoteBranchFormat(branch.BranchFormat):
1978
def __init__(self, network_name=None):
1979
super(RemoteBranchFormat, self).__init__()
1980
self._matchingbzrdir = RemoteBzrDirFormat()
1981
self._matchingbzrdir.set_branch_format(self)
1982
self._custom_format = None
1983
self._network_name = network_name
1985
def __eq__(self, other):
1986
return (isinstance(other, RemoteBranchFormat) and
1987
self.__dict__ == other.__dict__)
1989
def _ensure_real(self):
1990
if self._custom_format is None:
1991
self._custom_format = branch.network_format_registry.get(
1994
def get_format_description(self):
1995
return 'Remote BZR Branch'
1997
def network_name(self):
1998
return self._network_name
2000
def open(self, a_bzrdir, ignore_fallbacks=False):
2001
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2003
def _vfs_initialize(self, a_bzrdir):
2004
# Initialisation when using a local bzrdir object, or a non-vfs init
2005
# method is not available on the server.
2006
# self._custom_format is always set - the start of initialize ensures
2008
if isinstance(a_bzrdir, RemoteBzrDir):
2009
a_bzrdir._ensure_real()
2010
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2012
# We assume the bzrdir is parameterised; it may not be.
2013
result = self._custom_format.initialize(a_bzrdir)
2014
if (isinstance(a_bzrdir, RemoteBzrDir) and
2015
not isinstance(result, RemoteBranch)):
2016
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2019
def initialize(self, a_bzrdir):
2020
# 1) get the network name to use.
2021
if self._custom_format:
2022
network_name = self._custom_format.network_name()
2024
# Select the current bzrlib default and ask for that.
2025
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2026
reference_format = reference_bzrdir_format.get_branch_format()
2027
self._custom_format = reference_format
2028
network_name = reference_format.network_name()
2029
# Being asked to create on a non RemoteBzrDir:
2030
if not isinstance(a_bzrdir, RemoteBzrDir):
2031
return self._vfs_initialize(a_bzrdir)
2032
medium = a_bzrdir._client._medium
2033
if medium._is_remote_before((1, 13)):
2034
return self._vfs_initialize(a_bzrdir)
2035
# Creating on a remote bzr dir.
2036
# 2) try direct creation via RPC
2037
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2038
verb = 'BzrDir.create_branch'
2040
response = a_bzrdir._call(verb, path, network_name)
2041
except errors.UnknownSmartMethod:
2042
# Fallback - use vfs methods
2043
medium._remember_remote_is_before((1, 13))
2044
return self._vfs_initialize(a_bzrdir)
2045
if response[0] != 'ok':
2046
raise errors.UnexpectedSmartServerResponse(response)
2047
# Turn the response into a RemoteRepository object.
2048
format = RemoteBranchFormat(network_name=response[1])
2049
repo_format = response_tuple_to_repo_format(response[3:])
2050
if response[2] == '':
2051
repo_bzrdir = a_bzrdir
2053
repo_bzrdir = RemoteBzrDir(
2054
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2056
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2057
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2058
format=format, setup_stacking=False)
2059
# XXX: We know this is a new branch, so it must have revno 0, revid
2060
# NULL_REVISION. Creating the branch locked would make this be unable
2061
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2062
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2063
return remote_branch
2065
def make_tags(self, branch):
2067
return self._custom_format.make_tags(branch)
2069
def supports_tags(self):
2070
# Remote branches might support tags, but we won't know until we
2071
# access the real remote branch.
2073
return self._custom_format.supports_tags()
2075
def supports_stacking(self):
2077
return self._custom_format.supports_stacking()
2079
def supports_set_append_revisions_only(self):
2081
return self._custom_format.supports_set_append_revisions_only()
2084
class RemoteBranch(branch.Branch, _RpcHelper):
2085
"""Branch stored on a server accessed by HPSS RPC.
2087
At the moment most operations are mapped down to simple file operations.
2090
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2091
_client=None, format=None, setup_stacking=True):
2092
"""Create a RemoteBranch instance.
2094
:param real_branch: An optional local implementation of the branch
2095
format, usually accessing the data via the VFS.
2096
:param _client: Private parameter for testing.
2097
:param format: A RemoteBranchFormat object, None to create one
2098
automatically. If supplied it should have a network_name already
2100
:param setup_stacking: If True make an RPC call to determine the
2101
stacked (or not) status of the branch. If False assume the branch
2104
# We intentionally don't call the parent class's __init__, because it
2105
# will try to assign to self.tags, which is a property in this subclass.
2106
# And the parent's __init__ doesn't do much anyway.
2107
self.bzrdir = remote_bzrdir
2108
if _client is not None:
2109
self._client = _client
2111
self._client = remote_bzrdir._client
2112
self.repository = remote_repository
2113
if real_branch is not None:
2114
self._real_branch = real_branch
2115
# Give the remote repository the matching real repo.
2116
real_repo = self._real_branch.repository
2117
if isinstance(real_repo, RemoteRepository):
2118
real_repo._ensure_real()
2119
real_repo = real_repo._real_repository
2120
self.repository._set_real_repository(real_repo)
2121
# Give the branch the remote repository to let fast-pathing happen.
2122
self._real_branch.repository = self.repository
2124
self._real_branch = None
2125
# Fill out expected attributes of branch for bzrlib API users.
2126
self._clear_cached_state()
2127
self.base = self.bzrdir.root_transport.base
2128
self._control_files = None
2129
self._lock_mode = None
2130
self._lock_token = None
2131
self._repo_lock_token = None
2132
self._lock_count = 0
2133
self._leave_lock = False
2134
# Setup a format: note that we cannot call _ensure_real until all the
2135
# attributes above are set: This code cannot be moved higher up in this
2138
self._format = RemoteBranchFormat()
2139
if real_branch is not None:
2140
self._format._network_name = \
2141
self._real_branch._format.network_name()
2143
self._format = format
2144
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2145
# branch.open_branch method.
2146
self._real_ignore_fallbacks = not setup_stacking
2147
if not self._format._network_name:
2148
# Did not get from open_branchV2 - old server.
2150
self._format._network_name = \
2151
self._real_branch._format.network_name()
2152
self.tags = self._format.make_tags(self)
2153
# The base class init is not called, so we duplicate this:
2154
hooks = branch.Branch.hooks['open']
2157
self._is_stacked = False
2159
self._setup_stacking()
2161
def _setup_stacking(self):
2162
# configure stacking into the remote repository, by reading it from
2165
fallback_url = self.get_stacked_on_url()
2166
except (errors.NotStacked, errors.UnstackableBranchFormat,
2167
errors.UnstackableRepositoryFormat), e:
2169
self._is_stacked = True
2170
self._activate_fallback_location(fallback_url)
2172
def _get_config(self):
2173
return RemoteBranchConfig(self)
2175
def _get_real_transport(self):
2176
# if we try vfs access, return the real branch's vfs transport
2178
return self._real_branch._transport
2180
_transport = property(_get_real_transport)
2183
return "%s(%s)" % (self.__class__.__name__, self.base)
2187
def _ensure_real(self):
2188
"""Ensure that there is a _real_branch set.
2190
Used before calls to self._real_branch.
2192
if self._real_branch is None:
2193
if not vfs.vfs_enabled():
2194
raise AssertionError('smart server vfs must be enabled '
2195
'to use vfs implementation')
2196
self.bzrdir._ensure_real()
2197
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2198
ignore_fallbacks=self._real_ignore_fallbacks)
2199
if self.repository._real_repository is None:
2200
# Give the remote repository the matching real repo.
2201
real_repo = self._real_branch.repository
2202
if isinstance(real_repo, RemoteRepository):
2203
real_repo._ensure_real()
2204
real_repo = real_repo._real_repository
2205
self.repository._set_real_repository(real_repo)
2206
# Give the real branch the remote repository to let fast-pathing
2208
self._real_branch.repository = self.repository
2209
if self._lock_mode == 'r':
2210
self._real_branch.lock_read()
2211
elif self._lock_mode == 'w':
2212
self._real_branch.lock_write(token=self._lock_token)
2214
def _translate_error(self, err, **context):
2215
self.repository._translate_error(err, branch=self, **context)
2217
def _clear_cached_state(self):
2218
super(RemoteBranch, self)._clear_cached_state()
2219
if self._real_branch is not None:
2220
self._real_branch._clear_cached_state()
2222
def _clear_cached_state_of_remote_branch_only(self):
2223
"""Like _clear_cached_state, but doesn't clear the cache of
2226
This is useful when falling back to calling a method of
2227
self._real_branch that changes state. In that case the underlying
2228
branch changes, so we need to invalidate this RemoteBranch's cache of
2229
it. However, there's no need to invalidate the _real_branch's cache
2230
too, in fact doing so might harm performance.
2232
super(RemoteBranch, self)._clear_cached_state()
2235
def control_files(self):
2236
# Defer actually creating RemoteBranchLockableFiles until its needed,
2237
# because it triggers an _ensure_real that we otherwise might not need.
2238
if self._control_files is None:
2239
self._control_files = RemoteBranchLockableFiles(
2240
self.bzrdir, self._client)
2241
return self._control_files
2243
def _get_checkout_format(self):
2245
return self._real_branch._get_checkout_format()
2247
def get_physical_lock_status(self):
2248
"""See Branch.get_physical_lock_status()."""
2249
# should be an API call to the server, as branches must be lockable.
2251
return self._real_branch.get_physical_lock_status()
2253
def get_stacked_on_url(self):
2254
"""Get the URL this branch is stacked against.
2256
:raises NotStacked: If the branch is not stacked.
2257
:raises UnstackableBranchFormat: If the branch does not support
2259
:raises UnstackableRepositoryFormat: If the repository does not support
2263
# there may not be a repository yet, so we can't use
2264
# self._translate_error, so we can't use self._call either.
2265
response = self._client.call('Branch.get_stacked_on_url',
2266
self._remote_path())
2267
except errors.ErrorFromSmartServer, err:
2268
# there may not be a repository yet, so we can't call through
2269
# its _translate_error
2270
_translate_error(err, branch=self)
2271
except errors.UnknownSmartMethod, err:
2273
return self._real_branch.get_stacked_on_url()
2274
if response[0] != 'ok':
2275
raise errors.UnexpectedSmartServerResponse(response)
2278
def set_stacked_on_url(self, url):
2279
branch.Branch.set_stacked_on_url(self, url)
2281
self._is_stacked = False
2283
self._is_stacked = True
2285
def _vfs_get_tags_bytes(self):
2287
return self._real_branch._get_tags_bytes()
2289
def _get_tags_bytes(self):
2290
medium = self._client._medium
2291
if medium._is_remote_before((1, 13)):
2292
return self._vfs_get_tags_bytes()
2294
response = self._call('Branch.get_tags_bytes', self._remote_path())
2295
except errors.UnknownSmartMethod:
2296
medium._remember_remote_is_before((1, 13))
2297
return self._vfs_get_tags_bytes()
2300
def _vfs_set_tags_bytes(self, bytes):
2302
return self._real_branch._set_tags_bytes(bytes)
2304
def _set_tags_bytes(self, bytes):
2305
medium = self._client._medium
2306
if medium._is_remote_before((1, 18)):
2307
self._vfs_set_tags_bytes(bytes)
2311
self._remote_path(), self._lock_token, self._repo_lock_token)
2312
response = self._call_with_body_bytes(
2313
'Branch.set_tags_bytes', args, bytes)
2314
except errors.UnknownSmartMethod:
2315
medium._remember_remote_is_before((1, 18))
2316
self._vfs_set_tags_bytes(bytes)
2318
def lock_read(self):
2319
self.repository.lock_read()
2320
if not self._lock_mode:
2321
self._lock_mode = 'r'
2322
self._lock_count = 1
2323
if self._real_branch is not None:
2324
self._real_branch.lock_read()
2326
self._lock_count += 1
2328
def _remote_lock_write(self, token):
2330
branch_token = repo_token = ''
2332
branch_token = token
2333
repo_token = self.repository.lock_write()
2334
self.repository.unlock()
2335
err_context = {'token': token}
2336
response = self._call(
2337
'Branch.lock_write', self._remote_path(), branch_token,
2338
repo_token or '', **err_context)
2339
if response[0] != 'ok':
2340
raise errors.UnexpectedSmartServerResponse(response)
2341
ok, branch_token, repo_token = response
2342
return branch_token, repo_token
2344
def lock_write(self, token=None):
2345
if not self._lock_mode:
2346
# Lock the branch and repo in one remote call.
2347
remote_tokens = self._remote_lock_write(token)
2348
self._lock_token, self._repo_lock_token = remote_tokens
2349
if not self._lock_token:
2350
raise SmartProtocolError('Remote server did not return a token!')
2351
# Tell the self.repository object that it is locked.
2352
self.repository.lock_write(
2353
self._repo_lock_token, _skip_rpc=True)
2355
if self._real_branch is not None:
2356
self._real_branch.lock_write(token=self._lock_token)
2357
if token is not None:
2358
self._leave_lock = True
2360
self._leave_lock = False
2361
self._lock_mode = 'w'
2362
self._lock_count = 1
2363
elif self._lock_mode == 'r':
2364
raise errors.ReadOnlyTransaction
2366
if token is not None:
2367
# A token was given to lock_write, and we're relocking, so
2368
# check that the given token actually matches the one we
2370
if token != self._lock_token:
2371
raise errors.TokenMismatch(token, self._lock_token)
2372
self._lock_count += 1
2373
# Re-lock the repository too.
2374
self.repository.lock_write(self._repo_lock_token)
2375
return self._lock_token or None
2377
def _unlock(self, branch_token, repo_token):
2378
err_context = {'token': str((branch_token, repo_token))}
2379
response = self._call(
2380
'Branch.unlock', self._remote_path(), branch_token,
2381
repo_token or '', **err_context)
2382
if response == ('ok',):
2384
raise errors.UnexpectedSmartServerResponse(response)
2388
self._lock_count -= 1
2389
if not self._lock_count:
2390
self._clear_cached_state()
2391
mode = self._lock_mode
2392
self._lock_mode = None
2393
if self._real_branch is not None:
2394
if (not self._leave_lock and mode == 'w' and
2395
self._repo_lock_token):
2396
# If this RemoteBranch will remove the physical lock
2397
# for the repository, make sure the _real_branch
2398
# doesn't do it first. (Because the _real_branch's
2399
# repository is set to be the RemoteRepository.)
2400
self._real_branch.repository.leave_lock_in_place()
2401
self._real_branch.unlock()
2403
# Only write-locked branched need to make a remote method
2404
# call to perform the unlock.
2406
if not self._lock_token:
2407
raise AssertionError('Locked, but no token!')
2408
branch_token = self._lock_token
2409
repo_token = self._repo_lock_token
2410
self._lock_token = None
2411
self._repo_lock_token = None
2412
if not self._leave_lock:
2413
self._unlock(branch_token, repo_token)
2415
self.repository.unlock()
2417
def break_lock(self):
2419
return self._real_branch.break_lock()
2421
def leave_lock_in_place(self):
2422
if not self._lock_token:
2423
raise NotImplementedError(self.leave_lock_in_place)
2424
self._leave_lock = True
2426
def dont_leave_lock_in_place(self):
2427
if not self._lock_token:
2428
raise NotImplementedError(self.dont_leave_lock_in_place)
2429
self._leave_lock = False
2431
def get_rev_id(self, revno, history=None):
2433
return _mod_revision.NULL_REVISION
2434
last_revision_info = self.last_revision_info()
2435
ok, result = self.repository.get_rev_id_for_revno(
2436
revno, last_revision_info)
2439
missing_parent = result[1]
2440
# Either the revision named by the server is missing, or its parent
2441
# is. Call get_parent_map to determine which, so that we report a
2443
parent_map = self.repository.get_parent_map([missing_parent])
2444
if missing_parent in parent_map:
2445
missing_parent = parent_map[missing_parent]
2446
raise errors.RevisionNotPresent(missing_parent, self.repository)
2448
def _last_revision_info(self):
2449
response = self._call('Branch.last_revision_info', self._remote_path())
2450
if response[0] != 'ok':
2451
raise SmartProtocolError('unexpected response code %s' % (response,))
2452
revno = int(response[1])
2453
last_revision = response[2]
2454
return (revno, last_revision)
2456
def _gen_revision_history(self):
2457
"""See Branch._gen_revision_history()."""
2458
if self._is_stacked:
2460
return self._real_branch._gen_revision_history()
2461
response_tuple, response_handler = self._call_expecting_body(
2462
'Branch.revision_history', self._remote_path())
2463
if response_tuple[0] != 'ok':
2464
raise errors.UnexpectedSmartServerResponse(response_tuple)
2465
result = response_handler.read_body_bytes().split('\x00')
2470
def _remote_path(self):
2471
return self.bzrdir._path_for_remote_call(self._client)
2473
def _set_last_revision_descendant(self, revision_id, other_branch,
2474
allow_diverged=False, allow_overwrite_descendant=False):
2475
# This performs additional work to meet the hook contract; while its
2476
# undesirable, we have to synthesise the revno to call the hook, and
2477
# not calling the hook is worse as it means changes can't be prevented.
2478
# Having calculated this though, we can't just call into
2479
# set_last_revision_info as a simple call, because there is a set_rh
2480
# hook that some folk may still be using.
2481
old_revno, old_revid = self.last_revision_info()
2482
history = self._lefthand_history(revision_id)
2483
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2484
err_context = {'other_branch': other_branch}
2485
response = self._call('Branch.set_last_revision_ex',
2486
self._remote_path(), self._lock_token, self._repo_lock_token,
2487
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2489
self._clear_cached_state()
2490
if len(response) != 3 and response[0] != 'ok':
2491
raise errors.UnexpectedSmartServerResponse(response)
2492
new_revno, new_revision_id = response[1:]
2493
self._last_revision_info_cache = new_revno, new_revision_id
2494
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2495
if self._real_branch is not None:
2496
cache = new_revno, new_revision_id
2497
self._real_branch._last_revision_info_cache = cache
2499
def _set_last_revision(self, revision_id):
2500
old_revno, old_revid = self.last_revision_info()
2501
# This performs additional work to meet the hook contract; while its
2502
# undesirable, we have to synthesise the revno to call the hook, and
2503
# not calling the hook is worse as it means changes can't be prevented.
2504
# Having calculated this though, we can't just call into
2505
# set_last_revision_info as a simple call, because there is a set_rh
2506
# hook that some folk may still be using.
2507
history = self._lefthand_history(revision_id)
2508
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2509
self._clear_cached_state()
2510
response = self._call('Branch.set_last_revision',
2511
self._remote_path(), self._lock_token, self._repo_lock_token,
2513
if response != ('ok',):
2514
raise errors.UnexpectedSmartServerResponse(response)
2515
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2518
def set_revision_history(self, rev_history):
2519
# Send just the tip revision of the history; the server will generate
2520
# the full history from that. If the revision doesn't exist in this
2521
# branch, NoSuchRevision will be raised.
2522
if rev_history == []:
2525
rev_id = rev_history[-1]
2526
self._set_last_revision(rev_id)
2527
for hook in branch.Branch.hooks['set_rh']:
2528
hook(self, rev_history)
2529
self._cache_revision_history(rev_history)
2531
def _get_parent_location(self):
2532
medium = self._client._medium
2533
if medium._is_remote_before((1, 13)):
2534
return self._vfs_get_parent_location()
2536
response = self._call('Branch.get_parent', self._remote_path())
2537
except errors.UnknownSmartMethod:
2538
medium._remember_remote_is_before((1, 13))
2539
return self._vfs_get_parent_location()
2540
if len(response) != 1:
2541
raise errors.UnexpectedSmartServerResponse(response)
2542
parent_location = response[0]
2543
if parent_location == '':
2545
return parent_location
2547
def _vfs_get_parent_location(self):
2549
return self._real_branch._get_parent_location()
2551
def _set_parent_location(self, url):
2552
medium = self._client._medium
2553
if medium._is_remote_before((1, 15)):
2554
return self._vfs_set_parent_location(url)
2556
call_url = url or ''
2557
if type(call_url) is not str:
2558
raise AssertionError('url must be a str or None (%s)' % url)
2559
response = self._call('Branch.set_parent_location',
2560
self._remote_path(), self._lock_token, self._repo_lock_token,
2562
except errors.UnknownSmartMethod:
2563
medium._remember_remote_is_before((1, 15))
2564
return self._vfs_set_parent_location(url)
2566
raise errors.UnexpectedSmartServerResponse(response)
2568
def _vfs_set_parent_location(self, url):
2570
return self._real_branch._set_parent_location(url)
2573
def pull(self, source, overwrite=False, stop_revision=None,
2575
self._clear_cached_state_of_remote_branch_only()
2577
return self._real_branch.pull(
2578
source, overwrite=overwrite, stop_revision=stop_revision,
2579
_override_hook_target=self, **kwargs)
2582
def push(self, target, overwrite=False, stop_revision=None):
2584
return self._real_branch.push(
2585
target, overwrite=overwrite, stop_revision=stop_revision,
2586
_override_hook_source_branch=self)
2588
def is_locked(self):
2589
return self._lock_count >= 1
2592
def revision_id_to_revno(self, revision_id):
2594
return self._real_branch.revision_id_to_revno(revision_id)
2597
def set_last_revision_info(self, revno, revision_id):
2598
# XXX: These should be returned by the set_last_revision_info verb
2599
old_revno, old_revid = self.last_revision_info()
2600
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2601
revision_id = ensure_null(revision_id)
2603
response = self._call('Branch.set_last_revision_info',
2604
self._remote_path(), self._lock_token, self._repo_lock_token,
2605
str(revno), revision_id)
2606
except errors.UnknownSmartMethod:
2608
self._clear_cached_state_of_remote_branch_only()
2609
self._real_branch.set_last_revision_info(revno, revision_id)
2610
self._last_revision_info_cache = revno, revision_id
2612
if response == ('ok',):
2613
self._clear_cached_state()
2614
self._last_revision_info_cache = revno, revision_id
2615
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2616
# Update the _real_branch's cache too.
2617
if self._real_branch is not None:
2618
cache = self._last_revision_info_cache
2619
self._real_branch._last_revision_info_cache = cache
2621
raise errors.UnexpectedSmartServerResponse(response)
2624
def generate_revision_history(self, revision_id, last_rev=None,
2626
medium = self._client._medium
2627
if not medium._is_remote_before((1, 6)):
2628
# Use a smart method for 1.6 and above servers
2630
self._set_last_revision_descendant(revision_id, other_branch,
2631
allow_diverged=True, allow_overwrite_descendant=True)
2633
except errors.UnknownSmartMethod:
2634
medium._remember_remote_is_before((1, 6))
2635
self._clear_cached_state_of_remote_branch_only()
2636
self.set_revision_history(self._lefthand_history(revision_id,
2637
last_rev=last_rev,other_branch=other_branch))
2639
def set_push_location(self, location):
2641
return self._real_branch.set_push_location(location)
2644
class RemoteConfig(object):
2645
"""A Config that reads and writes from smart verbs.
2647
It is a low-level object that considers config data to be name/value pairs
2648
that may be associated with a section. Assigning meaning to the these
2649
values is done at higher levels like bzrlib.config.TreeConfig.
2652
def get_option(self, name, section=None, default=None):
2653
"""Return the value associated with a named option.
2655
:param name: The name of the value
2656
:param section: The section the option is in (if any)
2657
:param default: The value to return if the value is not set
2658
:return: The value or default value
2661
configobj = self._get_configobj()
2663
section_obj = configobj
2666
section_obj = configobj[section]
2669
return section_obj.get(name, default)
2670
except errors.UnknownSmartMethod:
2671
return self._vfs_get_option(name, section, default)
2673
def _response_to_configobj(self, response):
2674
if len(response[0]) and response[0][0] != 'ok':
2675
raise errors.UnexpectedSmartServerResponse(response)
2676
lines = response[1].read_body_bytes().splitlines()
2677
return config.ConfigObj(lines, encoding='utf-8')
2680
class RemoteBranchConfig(RemoteConfig):
2681
"""A RemoteConfig for Branches."""
2683
def __init__(self, branch):
2684
self._branch = branch
2686
def _get_configobj(self):
2687
path = self._branch._remote_path()
2688
response = self._branch._client.call_expecting_body(
2689
'Branch.get_config_file', path)
2690
return self._response_to_configobj(response)
2692
def set_option(self, value, name, section=None):
2693
"""Set the value associated with a named option.
2695
:param value: The value to set
2696
:param name: The name of the value to set
2697
:param section: The section the option is in (if any)
2699
medium = self._branch._client._medium
2700
if medium._is_remote_before((1, 14)):
2701
return self._vfs_set_option(value, name, section)
2703
path = self._branch._remote_path()
2704
response = self._branch._client.call('Branch.set_config_option',
2705
path, self._branch._lock_token, self._branch._repo_lock_token,
2706
value.encode('utf8'), name, section or '')
2707
except errors.UnknownSmartMethod:
2708
medium._remember_remote_is_before((1, 14))
2709
return self._vfs_set_option(value, name, section)
2711
raise errors.UnexpectedSmartServerResponse(response)
2713
def _real_object(self):
2714
self._branch._ensure_real()
2715
return self._branch._real_branch
2717
def _vfs_set_option(self, value, name, section=None):
2718
return self._real_object()._get_config().set_option(
2719
value, name, section)
2722
class RemoteBzrDirConfig(RemoteConfig):
2723
"""A RemoteConfig for BzrDirs."""
2725
def __init__(self, bzrdir):
2726
self._bzrdir = bzrdir
2728
def _get_configobj(self):
2729
medium = self._bzrdir._client._medium
2730
verb = 'BzrDir.get_config_file'
2731
if medium._is_remote_before((1, 15)):
2732
raise errors.UnknownSmartMethod(verb)
2733
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2734
response = self._bzrdir._call_expecting_body(
2736
return self._response_to_configobj(response)
2738
def _vfs_get_option(self, name, section, default):
2739
return self._real_object()._get_config().get_option(
2740
name, section, default)
2742
def set_option(self, value, name, section=None):
2743
"""Set the value associated with a named option.
2745
:param value: The value to set
2746
:param name: The name of the value to set
2747
:param section: The section the option is in (if any)
2749
return self._real_object()._get_config().set_option(
2750
value, name, section)
2752
def _real_object(self):
2753
self._bzrdir._ensure_real()
2754
return self._bzrdir._real_bzrdir
2758
def _extract_tar(tar, to_dir):
2759
"""Extract all the contents of a tarfile object.
2761
A replacement for extractall, which is not present in python2.4
2764
tar.extract(tarinfo, to_dir)
2767
def _translate_error(err, **context):
2768
"""Translate an ErrorFromSmartServer into a more useful error.
2770
Possible context keys:
2778
If the error from the server doesn't match a known pattern, then
2779
UnknownErrorFromSmartServer is raised.
2783
return context[name]
2784
except KeyError, key_err:
2785
mutter('Missing key %r in context %r', key_err.args[0], context)
2788
"""Get the path from the context if present, otherwise use first error
2792
return context['path']
2793
except KeyError, key_err:
2795
return err.error_args[0]
2796
except IndexError, idx_err:
2798
'Missing key %r in context %r', key_err.args[0], context)
2801
if err.error_verb == 'IncompatibleRepositories':
2802
raise errors.IncompatibleRepositories(err.error_args[0],
2803
err.error_args[1], err.error_args[2])
2804
elif err.error_verb == 'NoSuchRevision':
2805
raise NoSuchRevision(find('branch'), err.error_args[0])
2806
elif err.error_verb == 'nosuchrevision':
2807
raise NoSuchRevision(find('repository'), err.error_args[0])
2808
elif err.error_tuple == ('nobranch',):
2809
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2810
elif err.error_verb == 'norepository':
2811
raise errors.NoRepositoryPresent(find('bzrdir'))
2812
elif err.error_verb == 'LockContention':
2813
raise errors.LockContention('(remote lock)')
2814
elif err.error_verb == 'UnlockableTransport':
2815
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2816
elif err.error_verb == 'LockFailed':
2817
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2818
elif err.error_verb == 'TokenMismatch':
2819
raise errors.TokenMismatch(find('token'), '(remote token)')
2820
elif err.error_verb == 'Diverged':
2821
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2822
elif err.error_verb == 'TipChangeRejected':
2823
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2824
elif err.error_verb == 'UnstackableBranchFormat':
2825
raise errors.UnstackableBranchFormat(*err.error_args)
2826
elif err.error_verb == 'UnstackableRepositoryFormat':
2827
raise errors.UnstackableRepositoryFormat(*err.error_args)
2828
elif err.error_verb == 'NotStacked':
2829
raise errors.NotStacked(branch=find('branch'))
2830
elif err.error_verb == 'PermissionDenied':
2832
if len(err.error_args) >= 2:
2833
extra = err.error_args[1]
2836
raise errors.PermissionDenied(path, extra=extra)
2837
elif err.error_verb == 'ReadError':
2839
raise errors.ReadError(path)
2840
elif err.error_verb == 'NoSuchFile':
2842
raise errors.NoSuchFile(path)
2843
elif err.error_verb == 'FileExists':
2844
raise errors.FileExists(err.error_args[0])
2845
elif err.error_verb == 'DirectoryNotEmpty':
2846
raise errors.DirectoryNotEmpty(err.error_args[0])
2847
elif err.error_verb == 'ShortReadvError':
2848
args = err.error_args
2849
raise errors.ShortReadvError(
2850
args[0], int(args[1]), int(args[2]), int(args[3]))
2851
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2852
encoding = str(err.error_args[0]) # encoding must always be a string
2853
val = err.error_args[1]
2854
start = int(err.error_args[2])
2855
end = int(err.error_args[3])
2856
reason = str(err.error_args[4]) # reason must always be a string
2857
if val.startswith('u:'):
2858
val = val[2:].decode('utf-8')
2859
elif val.startswith('s:'):
2860
val = val[2:].decode('base64')
2861
if err.error_verb == 'UnicodeDecodeError':
2862
raise UnicodeDecodeError(encoding, val, start, end, reason)
2863
elif err.error_verb == 'UnicodeEncodeError':
2864
raise UnicodeEncodeError(encoding, val, start, end, reason)
2865
elif err.error_verb == 'ReadOnlyError':
2866
raise errors.TransportNotPossible('readonly transport')
2867
raise errors.UnknownErrorFromSmartServer(err)