1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
28
from bzrlib.branch import Branch, BranchReferenceFormat
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
33
from bzrlib.lockable_files import LockableFiles
34
from bzrlib.pack import ContainerReader
35
from bzrlib.revision import NULL_REVISION
36
from bzrlib.smart import client, vfs
37
from bzrlib.symbol_versioning import (
41
from bzrlib.trace import note
43
# Note: RemoteBzrDirFormat is in bzrdir.py
45
class RemoteBzrDir(BzrDir):
46
"""Control directory on a remote server, accessed via bzr:// or similar."""
48
def __init__(self, transport, _client=None):
49
"""Construct a RemoteBzrDir.
51
:param _client: Private parameter for testing. Disables probing and the
54
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
55
# this object holds a delegated bzrdir that uses file-level operations
56
# to talk to the other side
57
self._real_bzrdir = None
60
self._shared_medium = transport.get_shared_medium()
61
self._client = client._SmartClient(self._shared_medium)
63
self._client = _client
64
self._shared_medium = None
67
path = self._path_for_remote_call(self._client)
68
response = self._client.call('BzrDir.open', path)
69
if response not in [('yes',), ('no',)]:
70
raise errors.UnexpectedSmartServerResponse(response)
71
if response == ('no',):
72
raise errors.NotBranchError(path=transport.base)
74
def _ensure_real(self):
75
"""Ensure that there is a _real_bzrdir set.
77
Used before calls to self._real_bzrdir.
79
if not self._real_bzrdir:
80
self._real_bzrdir = BzrDir.open_from_transport(
81
self.root_transport, _server_formats=False)
83
def create_repository(self, shared=False):
85
self._real_bzrdir.create_repository(shared=shared)
86
return self.open_repository()
88
def create_branch(self):
90
real_branch = self._real_bzrdir.create_branch()
91
return RemoteBranch(self, self.find_repository(), real_branch)
93
def destroy_branch(self):
94
"""See BzrDir.destroy_branch"""
96
self._real_bzrdir.destroy_branch()
98
def create_workingtree(self, revision_id=None):
99
raise errors.NotLocalUrl(self.transport.base)
101
def find_branch_format(self):
102
"""Find the branch 'format' for this bzrdir.
104
This might be a synthetic object for e.g. RemoteBranch and SVN.
106
b = self.open_branch()
109
def get_branch_reference(self):
110
"""See BzrDir.get_branch_reference()."""
111
path = self._path_for_remote_call(self._client)
112
response = self._client.call('BzrDir.open_branch', path)
113
if response[0] == 'ok':
114
if response[1] == '':
115
# branch at this location.
118
# a branch reference, use the existing BranchReference logic.
120
elif response == ('nobranch',):
121
raise errors.NotBranchError(path=self.root_transport.base)
123
raise errors.UnexpectedSmartServerResponse(response)
125
def open_branch(self, _unsupported=False):
126
assert _unsupported == False, 'unsupported flag support not implemented yet.'
127
reference_url = self.get_branch_reference()
128
if reference_url is None:
129
# branch at this location.
130
return RemoteBranch(self, self.find_repository())
132
# a branch reference, use the existing BranchReference logic.
133
format = BranchReferenceFormat()
134
return format.open(self, _found=True, location=reference_url)
136
def open_repository(self):
137
path = self._path_for_remote_call(self._client)
138
response = self._client.call('BzrDir.find_repository', path)
139
assert response[0] in ('ok', 'norepository'), \
140
'unexpected response code %s' % (response,)
141
if response[0] == 'norepository':
142
raise errors.NoRepositoryPresent(self)
143
assert len(response) == 4, 'incorrect response length %s' % (response,)
144
if response[1] == '':
145
format = RemoteRepositoryFormat()
146
format.rich_root_data = (response[2] == 'yes')
147
format.supports_tree_reference = (response[3] == 'yes')
148
return RemoteRepository(self, format)
150
raise errors.NoRepositoryPresent(self)
152
def open_workingtree(self, recommend_upgrade=True):
154
if self._real_bzrdir.has_workingtree():
155
raise errors.NotLocalUrl(self.root_transport)
157
raise errors.NoWorkingTree(self.root_transport.base)
159
def _path_for_remote_call(self, client):
160
"""Return the path to be used for this bzrdir in a remote call."""
161
return client.remote_path_from_transport(self.root_transport)
163
def get_branch_transport(self, branch_format):
165
return self._real_bzrdir.get_branch_transport(branch_format)
167
def get_repository_transport(self, repository_format):
169
return self._real_bzrdir.get_repository_transport(repository_format)
171
def get_workingtree_transport(self, workingtree_format):
173
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
175
def can_convert_format(self):
176
"""Upgrading of remote bzrdirs is not supported yet."""
179
def needs_format_conversion(self, format=None):
180
"""Upgrading of remote bzrdirs is not supported yet."""
183
def clone(self, url, revision_id=None, force_new_repo=False):
185
return self._real_bzrdir.clone(url, revision_id=revision_id,
186
force_new_repo=force_new_repo)
189
class RemoteRepositoryFormat(repository.RepositoryFormat):
190
"""Format for repositories accessed over a _SmartClient.
192
Instances of this repository are represented by RemoteRepository
195
The RemoteRepositoryFormat is parameterised during construction
196
to reflect the capabilities of the real, remote format. Specifically
197
the attributes rich_root_data and supports_tree_reference are set
198
on a per instance basis, and are not set (and should not be) at
202
_matchingbzrdir = RemoteBzrDirFormat
204
def initialize(self, a_bzrdir, shared=False):
205
assert isinstance(a_bzrdir, RemoteBzrDir), \
206
'%r is not a RemoteBzrDir' % (a_bzrdir,)
207
return a_bzrdir.create_repository(shared=shared)
209
def open(self, a_bzrdir):
210
assert isinstance(a_bzrdir, RemoteBzrDir)
211
return a_bzrdir.open_repository()
213
def get_format_description(self):
214
return 'bzr remote repository'
216
def __eq__(self, other):
217
return self.__class__ == other.__class__
219
def check_conversion_target(self, target_format):
220
if self.rich_root_data and not target_format.rich_root_data:
221
raise errors.BadConversionTarget(
222
'Does not support rich root data.', target_format)
223
if (self.supports_tree_reference and
224
not getattr(target_format, 'supports_tree_reference', False)):
225
raise errors.BadConversionTarget(
226
'Does not support nested trees', target_format)
229
class RemoteRepository(object):
230
"""Repository accessed over rpc.
232
For the moment most operations are performed using local transport-backed
236
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
237
"""Create a RemoteRepository instance.
239
:param remote_bzrdir: The bzrdir hosting this repository.
240
:param format: The RemoteFormat object to use.
241
:param real_repository: If not None, a local implementation of the
242
repository logic for the repository, usually accessing the data
244
:param _client: Private testing parameter - override the smart client
245
to be used by the repository.
248
self._real_repository = real_repository
250
self._real_repository = None
251
self.bzrdir = remote_bzrdir
253
self._client = client._SmartClient(self.bzrdir._shared_medium)
255
self._client = _client
256
self._format = format
257
self._lock_mode = None
258
self._lock_token = None
260
self._leave_lock = False
262
# These depend on the actual remote format, so force them off for
263
# maximum compatibility. XXX: In future these should depend on the
264
# remote repository instance, but this is irrelevant until we perform
265
# reconcile via an RPC call.
266
self._reconcile_does_inventory_gc = False
267
self._reconcile_fixes_text_parents = False
268
self._reconcile_backsup_inventory = False
269
self.base = self.bzrdir.transport.base
272
return "%s(%s)" % (self.__class__.__name__, self.base)
276
def abort_write_group(self):
277
"""Complete a write group on the decorated repository.
279
Smart methods peform operations in a single step so this api
280
is not really applicable except as a compatibility thunk
281
for older plugins that don't use e.g. the CommitBuilder
285
return self._real_repository.abort_write_group()
287
def commit_write_group(self):
288
"""Complete a write group on the decorated repository.
290
Smart methods peform operations in a single step so this api
291
is not really applicable except as a compatibility thunk
292
for older plugins that don't use e.g. the CommitBuilder
296
return self._real_repository.commit_write_group()
298
def _ensure_real(self):
299
"""Ensure that there is a _real_repository set.
301
Used before calls to self._real_repository.
303
if not self._real_repository:
304
self.bzrdir._ensure_real()
305
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
306
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
308
def get_revision_graph(self, revision_id=None):
309
"""See Repository.get_revision_graph()."""
310
if revision_id is None:
312
elif revision_id == NULL_REVISION:
315
path = self.bzrdir._path_for_remote_call(self._client)
316
assert type(revision_id) is str
317
response = self._client.call_expecting_body(
318
'Repository.get_revision_graph', path, revision_id)
319
if response[0][0] not in ['ok', 'nosuchrevision']:
320
raise errors.UnexpectedSmartServerResponse(response[0])
321
if response[0][0] == 'ok':
322
coded = response[1].read_body_bytes()
324
# no revisions in this repository!
326
lines = coded.split('\n')
329
d = tuple(line.split())
330
revision_graph[d[0]] = d[1:]
332
return revision_graph
334
response_body = response[1].read_body_bytes()
335
assert response_body == ''
336
raise NoSuchRevision(self, revision_id)
338
def has_revision(self, revision_id):
339
"""See Repository.has_revision()."""
340
if revision_id is None:
341
# The null revision is always present.
343
path = self.bzrdir._path_for_remote_call(self._client)
344
response = self._client.call('Repository.has_revision', path, revision_id)
345
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
346
return response[0] == 'yes'
348
def has_same_location(self, other):
349
return (self.__class__ == other.__class__ and
350
self.bzrdir.transport.base == other.bzrdir.transport.base)
352
def get_graph(self, other_repository=None):
353
"""Return the graph for this repository format"""
355
return self._real_repository.get_graph(other_repository)
357
def gather_stats(self, revid=None, committers=None):
358
"""See Repository.gather_stats()."""
359
path = self.bzrdir._path_for_remote_call(self._client)
360
if revid in (None, NULL_REVISION):
364
if committers is None or not committers:
365
fmt_committers = 'no'
367
fmt_committers = 'yes'
368
response = self._client.call_expecting_body(
369
'Repository.gather_stats', path, fmt_revid, fmt_committers)
370
assert response[0][0] == 'ok', \
371
'unexpected response code %s' % (response[0],)
373
body = response[1].read_body_bytes()
375
for line in body.split('\n'):
378
key, val_text = line.split(':')
379
if key in ('revisions', 'size', 'committers'):
380
result[key] = int(val_text)
381
elif key in ('firstrev', 'latestrev'):
382
values = val_text.split(' ')[1:]
383
result[key] = (float(values[0]), long(values[1]))
387
def get_physical_lock_status(self):
388
"""See Repository.get_physical_lock_status()."""
391
def is_in_write_group(self):
392
"""Return True if there is an open write group.
394
write groups are only applicable locally for the smart server..
396
if self._real_repository:
397
return self._real_repository.is_in_write_group()
400
return self._lock_count >= 1
403
"""See Repository.is_shared()."""
404
path = self.bzrdir._path_for_remote_call(self._client)
405
response = self._client.call('Repository.is_shared', path)
406
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
407
return response[0] == 'yes'
409
def is_write_locked(self):
410
return self._lock_mode == 'w'
413
# wrong eventually - want a local lock cache context
414
if not self._lock_mode:
415
self._lock_mode = 'r'
417
if self._real_repository is not None:
418
self._real_repository.lock_read()
420
self._lock_count += 1
422
def _remote_lock_write(self, token):
423
path = self.bzrdir._path_for_remote_call(self._client)
426
response = self._client.call('Repository.lock_write', path, token)
427
if response[0] == 'ok':
430
elif response[0] == 'LockContention':
431
raise errors.LockContention('(remote lock)')
432
elif response[0] == 'UnlockableTransport':
433
raise errors.UnlockableTransport(self.bzrdir.root_transport)
434
elif response[0] == 'LockFailed':
435
raise errors.LockFailed(response[1], response[2])
437
raise errors.UnexpectedSmartServerResponse(response)
439
def lock_write(self, token=None):
440
if not self._lock_mode:
441
self._lock_token = self._remote_lock_write(token)
442
assert self._lock_token, 'Remote server did not return a token!'
443
if self._real_repository is not None:
444
self._real_repository.lock_write(token=self._lock_token)
445
if token is not None:
446
self._leave_lock = True
448
self._leave_lock = False
449
self._lock_mode = 'w'
451
elif self._lock_mode == 'r':
452
raise errors.ReadOnlyError(self)
454
self._lock_count += 1
455
return self._lock_token
457
def leave_lock_in_place(self):
458
self._leave_lock = True
460
def dont_leave_lock_in_place(self):
461
self._leave_lock = False
463
def _set_real_repository(self, repository):
464
"""Set the _real_repository for this repository.
466
:param repository: The repository to fallback to for non-hpss
467
implemented operations.
469
assert not isinstance(repository, RemoteRepository)
470
self._real_repository = repository
471
if self._lock_mode == 'w':
472
# if we are already locked, the real repository must be able to
473
# acquire the lock with our token.
474
self._real_repository.lock_write(self._lock_token)
475
elif self._lock_mode == 'r':
476
self._real_repository.lock_read()
478
def start_write_group(self):
479
"""Start a write group on the decorated repository.
481
Smart methods peform operations in a single step so this api
482
is not really applicable except as a compatibility thunk
483
for older plugins that don't use e.g. the CommitBuilder
487
return self._real_repository.start_write_group()
489
def _unlock(self, token):
490
path = self.bzrdir._path_for_remote_call(self._client)
491
response = self._client.call('Repository.unlock', path, token)
492
if response == ('ok',):
494
elif response[0] == 'TokenMismatch':
495
raise errors.TokenMismatch(token, '(remote token)')
497
raise errors.UnexpectedSmartServerResponse(response)
500
self._lock_count -= 1
501
if self._lock_count > 0:
503
old_mode = self._lock_mode
504
self._lock_mode = None
506
# The real repository is responsible at present for raising an
507
# exception if it's in an unfinished write group. However, it
508
# normally will *not* actually remove the lock from disk - that's
509
# done by the server on receiving the Repository.unlock call.
510
# This is just to let the _real_repository stay up to date.
511
if self._real_repository is not None:
512
self._real_repository.unlock()
514
# The rpc-level lock should be released even if there was a
515
# problem releasing the vfs-based lock.
517
# Only write-locked repositories need to make a remote method
518
# call to perfom the unlock.
519
assert self._lock_token, \
520
'%s is locked, but has no token' \
522
old_token = self._lock_token
523
self._lock_token = None
524
if not self._leave_lock:
525
self._unlock(old_token)
527
def break_lock(self):
528
# should hand off to the network
530
return self._real_repository.break_lock()
532
def _get_tarball(self, compression):
533
"""Return a TemporaryFile containing a repository tarball.
535
Returns None if the server does not support sending tarballs.
538
path = self.bzrdir._path_for_remote_call(self._client)
539
response, protocol = self._client.call_expecting_body(
540
'Repository.tarball', path, compression)
541
if response[0] == 'ok':
542
# Extract the tarball and return it
543
t = tempfile.NamedTemporaryFile()
544
# TODO: rpc layer should read directly into it...
545
t.write(protocol.read_body_bytes())
548
if (response == ('error', "Generic bzr smart protocol error: "
549
"bad request 'Repository.tarball'") or
550
response == ('error', "Generic bzr smart protocol error: "
551
"bad request u'Repository.tarball'")):
552
protocol.cancel_read_body()
554
raise errors.UnexpectedSmartServerResponse(response)
556
def sprout(self, to_bzrdir, revision_id=None):
557
# TODO: Option to control what format is created?
558
dest_repo = to_bzrdir.create_repository()
559
dest_repo.fetch(self, revision_id=revision_id)
562
### These methods are just thin shims to the VFS object for now.
564
def revision_tree(self, revision_id):
566
return self._real_repository.revision_tree(revision_id)
568
def get_serializer_format(self):
570
return self._real_repository.get_serializer_format()
572
def get_commit_builder(self, branch, parents, config, timestamp=None,
573
timezone=None, committer=None, revprops=None,
575
# FIXME: It ought to be possible to call this without immediately
576
# triggering _ensure_real. For now it's the easiest thing to do.
578
builder = self._real_repository.get_commit_builder(branch, parents,
579
config, timestamp=timestamp, timezone=timezone,
580
committer=committer, revprops=revprops, revision_id=revision_id)
581
# Make the builder use this RemoteRepository rather than the real one.
582
builder.repository = self
586
def add_inventory(self, revid, inv, parents):
588
return self._real_repository.add_inventory(revid, inv, parents)
591
def add_revision(self, rev_id, rev, inv=None, config=None):
593
return self._real_repository.add_revision(
594
rev_id, rev, inv=inv, config=config)
597
def get_inventory(self, revision_id):
599
return self._real_repository.get_inventory(revision_id)
602
def get_revision(self, revision_id):
604
return self._real_repository.get_revision(revision_id)
607
def weave_store(self):
609
return self._real_repository.weave_store
611
def get_transaction(self):
613
return self._real_repository.get_transaction()
616
def clone(self, a_bzrdir, revision_id=None):
618
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
620
def make_working_trees(self):
621
"""RemoteRepositories never create working trees by default."""
624
def fetch(self, source, revision_id=None, pb=None):
625
if self.has_same_location(source):
626
# check that last_revision is in 'from' and then return a
628
if (revision_id is not None and
629
not _mod_revision.is_null(revision_id)):
630
self.get_revision(revision_id)
633
return self._real_repository.fetch(
634
source, revision_id=revision_id, pb=pb)
636
def create_bundle(self, target, base, fileobj, format=None):
638
self._real_repository.create_bundle(target, base, fileobj, format)
641
def control_weaves(self):
643
return self._real_repository.control_weaves
646
def get_ancestry(self, revision_id, topo_sorted=True):
648
return self._real_repository.get_ancestry(revision_id, topo_sorted)
651
def get_inventory_weave(self):
653
return self._real_repository.get_inventory_weave()
655
def fileids_altered_by_revision_ids(self, revision_ids):
657
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
659
def get_versioned_file_checker(self, revisions, revision_versions_cache):
661
return self._real_repository.get_versioned_file_checker(
662
revisions, revision_versions_cache)
664
def iter_files_bytes(self, desired_files):
665
"""See Repository.iter_file_bytes.
668
return self._real_repository.iter_files_bytes(desired_files)
671
def get_signature_text(self, revision_id):
673
return self._real_repository.get_signature_text(revision_id)
676
def get_revision_graph_with_ghosts(self, revision_ids=None):
678
return self._real_repository.get_revision_graph_with_ghosts(
679
revision_ids=revision_ids)
682
def get_inventory_xml(self, revision_id):
684
return self._real_repository.get_inventory_xml(revision_id)
686
def deserialise_inventory(self, revision_id, xml):
688
return self._real_repository.deserialise_inventory(revision_id, xml)
690
def reconcile(self, other=None, thorough=False):
692
return self._real_repository.reconcile(other=other, thorough=thorough)
694
def all_revision_ids(self):
696
return self._real_repository.all_revision_ids()
699
def get_deltas_for_revisions(self, revisions):
701
return self._real_repository.get_deltas_for_revisions(revisions)
704
def get_revision_delta(self, revision_id):
706
return self._real_repository.get_revision_delta(revision_id)
709
def revision_trees(self, revision_ids):
711
return self._real_repository.revision_trees(revision_ids)
714
def get_revision_reconcile(self, revision_id):
716
return self._real_repository.get_revision_reconcile(revision_id)
719
def check(self, revision_ids=None):
721
return self._real_repository.check(revision_ids=revision_ids)
723
def copy_content_into(self, destination, revision_id=None):
725
return self._real_repository.copy_content_into(
726
destination, revision_id=revision_id)
728
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
729
# get a tarball of the remote repository, and copy from that into the
731
from bzrlib import osutils
734
from StringIO import StringIO
735
# TODO: Maybe a progress bar while streaming the tarball?
736
note("Copying repository content as tarball...")
737
tar_file = self._get_tarball('bz2')
740
destination = to_bzrdir.create_repository()
742
tar = tarfile.open('repository', fileobj=tar_file,
744
tmpdir = tempfile.mkdtemp()
746
_extract_tar(tar, tmpdir)
747
tmp_bzrdir = BzrDir.open(tmpdir)
748
tmp_repo = tmp_bzrdir.open_repository()
749
tmp_repo.copy_content_into(destination, revision_id)
751
osutils.rmtree(tmpdir)
755
# TODO: Suggestion from john: using external tar is much faster than
756
# python's tarfile library, but it may not work on windows.
760
"""Compress the data within the repository.
762
This is not currently implemented within the smart server.
765
return self._real_repository.pack()
767
def set_make_working_trees(self, new_value):
768
raise NotImplementedError(self.set_make_working_trees)
771
def sign_revision(self, revision_id, gpg_strategy):
773
return self._real_repository.sign_revision(revision_id, gpg_strategy)
776
def get_revisions(self, revision_ids):
778
return self._real_repository.get_revisions(revision_ids)
780
def supports_rich_root(self):
782
return self._real_repository.supports_rich_root()
784
def iter_reverse_revision_history(self, revision_id):
786
return self._real_repository.iter_reverse_revision_history(revision_id)
789
def _serializer(self):
791
return self._real_repository._serializer
793
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
795
return self._real_repository.store_revision_signature(
796
gpg_strategy, plaintext, revision_id)
798
def has_signature_for_revision_id(self, revision_id):
800
return self._real_repository.has_signature_for_revision_id(revision_id)
802
def get_data_stream(self, revision_ids):
803
path = self.bzrdir._path_for_remote_call(self._client)
804
response, protocol = self._client.call_expecting_body(
805
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
806
if response == ('ok',):
807
return self._deserialise_stream(protocol)
808
elif (response == ('error', "Generic bzr smart protocol error: "
809
"bad request 'Repository.stream_knit_data_for_revisions'") or
810
response == ('error', "Generic bzr smart protocol error: "
811
"bad request u'Repository.stream_knit_data_for_revisions'")):
812
protocol.cancel_read_body()
814
return self._real_repository.get_data_stream(revision_ids)
816
raise errors.UnexpectedSmartServerResponse(response)
818
def _deserialise_stream(self, protocol):
819
buffer = StringIO(protocol.read_body_bytes())
820
reader = ContainerReader(buffer)
821
for record_names, read_bytes in reader.iter_records():
823
# These records should have only one name, and that name
824
# should be a one-element tuple.
825
[name_tuple] = record_names
827
raise errors.SmartProtocolError(
828
'Repository data stream had invalid record name %r'
830
yield name_tuple, read_bytes(None)
832
def insert_data_stream(self, stream):
834
self._real_repository.insert_data_stream(stream)
836
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
838
return self._real_repository.item_keys_introduced_by(revision_ids,
841
def revision_graph_can_have_wrong_parents(self):
842
# The answer depends on the remote repo format.
844
return self._real_repository.revision_graph_can_have_wrong_parents()
846
def _find_inconsistent_revision_parents(self):
848
return self._real_repository._find_inconsistent_revision_parents()
850
def _check_for_inconsistent_revision_parents(self):
852
return self._real_repository._check_for_inconsistent_revision_parents()
855
class RemoteBranchLockableFiles(LockableFiles):
856
"""A 'LockableFiles' implementation that talks to a smart server.
858
This is not a public interface class.
861
def __init__(self, bzrdir, _client):
863
self._client = _client
864
self._need_find_modes = True
865
LockableFiles.__init__(
866
self, bzrdir.get_branch_transport(None),
867
'lock', lockdir.LockDir)
869
def _find_modes(self):
870
# RemoteBranches don't let the client set the mode of control files.
871
self._dir_mode = None
872
self._file_mode = None
875
"""'get' a remote path as per the LockableFiles interface.
877
:param path: the file to 'get'. If this is 'branch.conf', we do not
878
just retrieve a file, instead we ask the smart server to generate
879
a configuration for us - which is retrieved as an INI file.
881
if path == 'branch.conf':
882
path = self.bzrdir._path_for_remote_call(self._client)
883
response = self._client.call_expecting_body(
884
'Branch.get_config_file', path)
885
assert response[0][0] == 'ok', \
886
'unexpected response code %s' % (response[0],)
887
return StringIO(response[1].read_body_bytes())
890
return LockableFiles.get(self, path)
893
class RemoteBranchFormat(branch.BranchFormat):
895
def __eq__(self, other):
896
return (isinstance(other, RemoteBranchFormat) and
897
self.__dict__ == other.__dict__)
899
def get_format_description(self):
900
return 'Remote BZR Branch'
902
def get_format_string(self):
903
return 'Remote BZR Branch'
905
def open(self, a_bzrdir):
906
assert isinstance(a_bzrdir, RemoteBzrDir)
907
return a_bzrdir.open_branch()
909
def initialize(self, a_bzrdir):
910
assert isinstance(a_bzrdir, RemoteBzrDir)
911
return a_bzrdir.create_branch()
913
def supports_tags(self):
914
# Remote branches might support tags, but we won't know until we
915
# access the real remote branch.
919
class RemoteBranch(branch.Branch):
920
"""Branch stored on a server accessed by HPSS RPC.
922
At the moment most operations are mapped down to simple file operations.
925
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
927
"""Create a RemoteBranch instance.
929
:param real_branch: An optional local implementation of the branch
930
format, usually accessing the data via the VFS.
931
:param _client: Private parameter for testing.
933
# We intentionally don't call the parent class's __init__, because it
934
# will try to assign to self.tags, which is a property in this subclass.
935
# And the parent's __init__ doesn't do much anyway.
936
self._revision_history_cache = None
937
self.bzrdir = remote_bzrdir
938
if _client is not None:
939
self._client = _client
941
self._client = client._SmartClient(self.bzrdir._shared_medium)
942
self.repository = remote_repository
943
if real_branch is not None:
944
self._real_branch = real_branch
945
# Give the remote repository the matching real repo.
946
real_repo = self._real_branch.repository
947
if isinstance(real_repo, RemoteRepository):
948
real_repo._ensure_real()
949
real_repo = real_repo._real_repository
950
self.repository._set_real_repository(real_repo)
951
# Give the branch the remote repository to let fast-pathing happen.
952
self._real_branch.repository = self.repository
954
self._real_branch = None
955
# Fill out expected attributes of branch for bzrlib api users.
956
self._format = RemoteBranchFormat()
957
self.base = self.bzrdir.root_transport.base
958
self._control_files = None
959
self._lock_mode = None
960
self._lock_token = None
962
self._leave_lock = False
965
return "%s(%s)" % (self.__class__.__name__, self.base)
969
def _ensure_real(self):
970
"""Ensure that there is a _real_branch set.
972
Used before calls to self._real_branch.
974
if not self._real_branch:
975
assert vfs.vfs_enabled()
976
self.bzrdir._ensure_real()
977
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
978
# Give the remote repository the matching real repo.
979
real_repo = self._real_branch.repository
980
if isinstance(real_repo, RemoteRepository):
981
real_repo._ensure_real()
982
real_repo = real_repo._real_repository
983
self.repository._set_real_repository(real_repo)
984
# Give the branch the remote repository to let fast-pathing happen.
985
self._real_branch.repository = self.repository
986
# XXX: deal with _lock_mode == 'w'
987
if self._lock_mode == 'r':
988
self._real_branch.lock_read()
991
def control_files(self):
992
# Defer actually creating RemoteBranchLockableFiles until its needed,
993
# because it triggers an _ensure_real that we otherwise might not need.
994
if self._control_files is None:
995
self._control_files = RemoteBranchLockableFiles(
996
self.bzrdir, self._client)
997
return self._control_files
999
def _get_checkout_format(self):
1001
return self._real_branch._get_checkout_format()
1003
def get_physical_lock_status(self):
1004
"""See Branch.get_physical_lock_status()."""
1005
# should be an API call to the server, as branches must be lockable.
1007
return self._real_branch.get_physical_lock_status()
1009
def lock_read(self):
1010
if not self._lock_mode:
1011
self._lock_mode = 'r'
1012
self._lock_count = 1
1013
if self._real_branch is not None:
1014
self._real_branch.lock_read()
1016
self._lock_count += 1
1018
def _remote_lock_write(self, token):
1020
branch_token = repo_token = ''
1022
branch_token = token
1023
repo_token = self.repository.lock_write()
1024
self.repository.unlock()
1025
path = self.bzrdir._path_for_remote_call(self._client)
1026
response = self._client.call('Branch.lock_write', path, branch_token,
1028
if response[0] == 'ok':
1029
ok, branch_token, repo_token = response
1030
return branch_token, repo_token
1031
elif response[0] == 'LockContention':
1032
raise errors.LockContention('(remote lock)')
1033
elif response[0] == 'TokenMismatch':
1034
raise errors.TokenMismatch(token, '(remote token)')
1035
elif response[0] == 'UnlockableTransport':
1036
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1037
elif response[0] == 'ReadOnlyError':
1038
raise errors.ReadOnlyError(self)
1039
elif response[0] == 'LockFailed':
1040
raise errors.LockFailed(response[1], response[2])
1042
raise errors.UnexpectedSmartServerResponse(response)
1044
def lock_write(self, token=None):
1045
if not self._lock_mode:
1046
remote_tokens = self._remote_lock_write(token)
1047
self._lock_token, self._repo_lock_token = remote_tokens
1048
assert self._lock_token, 'Remote server did not return a token!'
1049
# TODO: We really, really, really don't want to call _ensure_real
1050
# here, but it's the easiest way to ensure coherency between the
1051
# state of the RemoteBranch and RemoteRepository objects and the
1052
# physical locks. If we don't materialise the real objects here,
1053
# then getting everything in the right state later is complex, so
1054
# for now we just do it the lazy way.
1055
# -- Andrew Bennetts, 2007-02-22.
1057
if self._real_branch is not None:
1058
self._real_branch.repository.lock_write(
1059
token=self._repo_lock_token)
1061
self._real_branch.lock_write(token=self._lock_token)
1063
self._real_branch.repository.unlock()
1064
if token is not None:
1065
self._leave_lock = True
1067
# XXX: this case seems to be unreachable; token cannot be None.
1068
self._leave_lock = False
1069
self._lock_mode = 'w'
1070
self._lock_count = 1
1071
elif self._lock_mode == 'r':
1072
raise errors.ReadOnlyTransaction
1074
if token is not None:
1075
# A token was given to lock_write, and we're relocking, so check
1076
# that the given token actually matches the one we already have.
1077
if token != self._lock_token:
1078
raise errors.TokenMismatch(token, self._lock_token)
1079
self._lock_count += 1
1080
return self._lock_token
1082
def _unlock(self, branch_token, repo_token):
1083
path = self.bzrdir._path_for_remote_call(self._client)
1084
response = self._client.call('Branch.unlock', path, branch_token,
1086
if response == ('ok',):
1088
elif response[0] == 'TokenMismatch':
1089
raise errors.TokenMismatch(
1090
str((branch_token, repo_token)), '(remote tokens)')
1092
raise errors.UnexpectedSmartServerResponse(response)
1095
self._lock_count -= 1
1096
if not self._lock_count:
1097
self._clear_cached_state()
1098
mode = self._lock_mode
1099
self._lock_mode = None
1100
if self._real_branch is not None:
1101
if not self._leave_lock:
1102
# If this RemoteBranch will remove the physical lock for the
1103
# repository, make sure the _real_branch doesn't do it
1104
# first. (Because the _real_branch's repository is set to
1105
# be the RemoteRepository.)
1106
self._real_branch.repository.leave_lock_in_place()
1107
self._real_branch.unlock()
1109
# Only write-locked branched need to make a remote method call
1110
# to perfom the unlock.
1112
assert self._lock_token, 'Locked, but no token!'
1113
branch_token = self._lock_token
1114
repo_token = self._repo_lock_token
1115
self._lock_token = None
1116
self._repo_lock_token = None
1117
if not self._leave_lock:
1118
self._unlock(branch_token, repo_token)
1120
def break_lock(self):
1122
return self._real_branch.break_lock()
1124
def leave_lock_in_place(self):
1125
self._leave_lock = True
1127
def dont_leave_lock_in_place(self):
1128
self._leave_lock = False
1130
def last_revision_info(self):
1131
"""See Branch.last_revision_info()."""
1132
path = self.bzrdir._path_for_remote_call(self._client)
1133
response = self._client.call('Branch.last_revision_info', path)
1134
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1135
revno = int(response[1])
1136
last_revision = response[2]
1137
return (revno, last_revision)
1139
def _gen_revision_history(self):
1140
"""See Branch._gen_revision_history()."""
1141
path = self.bzrdir._path_for_remote_call(self._client)
1142
response = self._client.call_expecting_body(
1143
'Branch.revision_history', path)
1144
assert response[0][0] == 'ok', ('unexpected response code %s'
1146
result = response[1].read_body_bytes().split('\x00')
1152
def set_revision_history(self, rev_history):
1153
# Send just the tip revision of the history; the server will generate
1154
# the full history from that. If the revision doesn't exist in this
1155
# branch, NoSuchRevision will be raised.
1156
path = self.bzrdir._path_for_remote_call(self._client)
1157
if rev_history == []:
1160
rev_id = rev_history[-1]
1161
self._clear_cached_state()
1162
response = self._client.call('Branch.set_last_revision',
1163
path, self._lock_token, self._repo_lock_token, rev_id)
1164
if response[0] == 'NoSuchRevision':
1165
raise NoSuchRevision(self, rev_id)
1167
assert response == ('ok',), (
1168
'unexpected response code %r' % (response,))
1169
self._cache_revision_history(rev_history)
1171
def get_parent(self):
1173
return self._real_branch.get_parent()
1175
def set_parent(self, url):
1177
return self._real_branch.set_parent(url)
1179
def get_config(self):
1180
return RemoteBranchConfig(self)
1182
def sprout(self, to_bzrdir, revision_id=None):
1183
# Like Branch.sprout, except that it sprouts a branch in the default
1184
# format, because RemoteBranches can't be created at arbitrary URLs.
1185
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1186
# to_bzrdir.create_branch...
1187
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1188
self.copy_content_into(result, revision_id=revision_id)
1189
result.set_parent(self.bzrdir.root_transport.base)
1193
def pull(self, source, overwrite=False, stop_revision=None,
1195
# FIXME: This asks the real branch to run the hooks, which means
1196
# they're called with the wrong target branch parameter.
1197
# The test suite specifically allows this at present but it should be
1198
# fixed. It should get a _override_hook_target branch,
1199
# as push does. -- mbp 20070405
1201
self._real_branch.pull(
1202
source, overwrite=overwrite, stop_revision=stop_revision,
1206
def push(self, target, overwrite=False, stop_revision=None):
1208
return self._real_branch.push(
1209
target, overwrite=overwrite, stop_revision=stop_revision,
1210
_override_hook_source_branch=self)
1212
def is_locked(self):
1213
return self._lock_count >= 1
1215
def set_last_revision_info(self, revno, revision_id):
1217
self._clear_cached_state()
1218
return self._real_branch.set_last_revision_info(revno, revision_id)
1220
def generate_revision_history(self, revision_id, last_rev=None,
1223
return self._real_branch.generate_revision_history(
1224
revision_id, last_rev=last_rev, other_branch=other_branch)
1229
return self._real_branch.tags
1231
def set_push_location(self, location):
1233
return self._real_branch.set_push_location(location)
1235
def update_revisions(self, other, stop_revision=None):
1237
return self._real_branch.update_revisions(
1238
other, stop_revision=stop_revision)
1241
class RemoteBranchConfig(BranchConfig):
1244
self.branch._ensure_real()
1245
return self.branch._real_branch.get_config().username()
1247
def _get_branch_data_config(self):
1248
self.branch._ensure_real()
1249
if self._branch_data_config is None:
1250
self._branch_data_config = TreeConfig(self.branch._real_branch)
1251
return self._branch_data_config
1254
def _extract_tar(tar, to_dir):
1255
"""Extract all the contents of a tarfile object.
1257
A replacement for extractall, which is not present in python2.4
1260
tar.extract(tarinfo, to_dir)