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_id_to_revno_cache = None
937
self._revision_history_cache = None
938
self.bzrdir = remote_bzrdir
939
if _client is not None:
940
self._client = _client
942
self._client = client._SmartClient(self.bzrdir._shared_medium)
943
self.repository = remote_repository
944
if real_branch is not None:
945
self._real_branch = real_branch
946
# Give the remote repository the matching real repo.
947
real_repo = self._real_branch.repository
948
if isinstance(real_repo, RemoteRepository):
949
real_repo._ensure_real()
950
real_repo = real_repo._real_repository
951
self.repository._set_real_repository(real_repo)
952
# Give the branch the remote repository to let fast-pathing happen.
953
self._real_branch.repository = self.repository
955
self._real_branch = None
956
# Fill out expected attributes of branch for bzrlib api users.
957
self._format = RemoteBranchFormat()
958
self.base = self.bzrdir.root_transport.base
959
self._control_files = None
960
self._lock_mode = None
961
self._lock_token = None
963
self._leave_lock = False
966
return "%s(%s)" % (self.__class__.__name__, self.base)
970
def _ensure_real(self):
971
"""Ensure that there is a _real_branch set.
973
Used before calls to self._real_branch.
975
if not self._real_branch:
976
assert vfs.vfs_enabled()
977
self.bzrdir._ensure_real()
978
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
979
# Give the remote repository the matching real repo.
980
real_repo = self._real_branch.repository
981
if isinstance(real_repo, RemoteRepository):
982
real_repo._ensure_real()
983
real_repo = real_repo._real_repository
984
self.repository._set_real_repository(real_repo)
985
# Give the branch the remote repository to let fast-pathing happen.
986
self._real_branch.repository = self.repository
987
# XXX: deal with _lock_mode == 'w'
988
if self._lock_mode == 'r':
989
self._real_branch.lock_read()
992
def control_files(self):
993
# Defer actually creating RemoteBranchLockableFiles until its needed,
994
# because it triggers an _ensure_real that we otherwise might not need.
995
if self._control_files is None:
996
self._control_files = RemoteBranchLockableFiles(
997
self.bzrdir, self._client)
998
return self._control_files
1000
def _get_checkout_format(self):
1002
return self._real_branch._get_checkout_format()
1004
def get_physical_lock_status(self):
1005
"""See Branch.get_physical_lock_status()."""
1006
# should be an API call to the server, as branches must be lockable.
1008
return self._real_branch.get_physical_lock_status()
1010
def lock_read(self):
1011
if not self._lock_mode:
1012
self._lock_mode = 'r'
1013
self._lock_count = 1
1014
if self._real_branch is not None:
1015
self._real_branch.lock_read()
1017
self._lock_count += 1
1019
def _remote_lock_write(self, token):
1021
branch_token = repo_token = ''
1023
branch_token = token
1024
repo_token = self.repository.lock_write()
1025
self.repository.unlock()
1026
path = self.bzrdir._path_for_remote_call(self._client)
1027
response = self._client.call('Branch.lock_write', path, branch_token,
1029
if response[0] == 'ok':
1030
ok, branch_token, repo_token = response
1031
return branch_token, repo_token
1032
elif response[0] == 'LockContention':
1033
raise errors.LockContention('(remote lock)')
1034
elif response[0] == 'TokenMismatch':
1035
raise errors.TokenMismatch(token, '(remote token)')
1036
elif response[0] == 'UnlockableTransport':
1037
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1038
elif response[0] == 'ReadOnlyError':
1039
raise errors.ReadOnlyError(self)
1040
elif response[0] == 'LockFailed':
1041
raise errors.LockFailed(response[1], response[2])
1043
raise errors.UnexpectedSmartServerResponse(response)
1045
def lock_write(self, token=None):
1046
if not self._lock_mode:
1047
remote_tokens = self._remote_lock_write(token)
1048
self._lock_token, self._repo_lock_token = remote_tokens
1049
assert self._lock_token, 'Remote server did not return a token!'
1050
# TODO: We really, really, really don't want to call _ensure_real
1051
# here, but it's the easiest way to ensure coherency between the
1052
# state of the RemoteBranch and RemoteRepository objects and the
1053
# physical locks. If we don't materialise the real objects here,
1054
# then getting everything in the right state later is complex, so
1055
# for now we just do it the lazy way.
1056
# -- Andrew Bennetts, 2007-02-22.
1058
if self._real_branch is not None:
1059
self._real_branch.repository.lock_write(
1060
token=self._repo_lock_token)
1062
self._real_branch.lock_write(token=self._lock_token)
1064
self._real_branch.repository.unlock()
1065
if token is not None:
1066
self._leave_lock = True
1068
# XXX: this case seems to be unreachable; token cannot be None.
1069
self._leave_lock = False
1070
self._lock_mode = 'w'
1071
self._lock_count = 1
1072
elif self._lock_mode == 'r':
1073
raise errors.ReadOnlyTransaction
1075
if token is not None:
1076
# A token was given to lock_write, and we're relocking, so check
1077
# that the given token actually matches the one we already have.
1078
if token != self._lock_token:
1079
raise errors.TokenMismatch(token, self._lock_token)
1080
self._lock_count += 1
1081
return self._lock_token
1083
def _unlock(self, branch_token, repo_token):
1084
path = self.bzrdir._path_for_remote_call(self._client)
1085
response = self._client.call('Branch.unlock', path, branch_token,
1087
if response == ('ok',):
1089
elif response[0] == 'TokenMismatch':
1090
raise errors.TokenMismatch(
1091
str((branch_token, repo_token)), '(remote tokens)')
1093
raise errors.UnexpectedSmartServerResponse(response)
1096
self._lock_count -= 1
1097
if not self._lock_count:
1098
self._clear_cached_state()
1099
mode = self._lock_mode
1100
self._lock_mode = None
1101
if self._real_branch is not None:
1102
if not self._leave_lock:
1103
# If this RemoteBranch will remove the physical lock for the
1104
# repository, make sure the _real_branch doesn't do it
1105
# first. (Because the _real_branch's repository is set to
1106
# be the RemoteRepository.)
1107
self._real_branch.repository.leave_lock_in_place()
1108
self._real_branch.unlock()
1110
# Only write-locked branched need to make a remote method call
1111
# to perfom the unlock.
1113
assert self._lock_token, 'Locked, but no token!'
1114
branch_token = self._lock_token
1115
repo_token = self._repo_lock_token
1116
self._lock_token = None
1117
self._repo_lock_token = None
1118
if not self._leave_lock:
1119
self._unlock(branch_token, repo_token)
1121
def break_lock(self):
1123
return self._real_branch.break_lock()
1125
def leave_lock_in_place(self):
1126
self._leave_lock = True
1128
def dont_leave_lock_in_place(self):
1129
self._leave_lock = False
1131
def last_revision_info(self):
1132
"""See Branch.last_revision_info()."""
1133
path = self.bzrdir._path_for_remote_call(self._client)
1134
response = self._client.call('Branch.last_revision_info', path)
1135
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1136
revno = int(response[1])
1137
last_revision = response[2]
1138
return (revno, last_revision)
1140
def _gen_revision_history(self):
1141
"""See Branch._gen_revision_history()."""
1142
path = self.bzrdir._path_for_remote_call(self._client)
1143
response = self._client.call_expecting_body(
1144
'Branch.revision_history', path)
1145
assert response[0][0] == 'ok', ('unexpected response code %s'
1147
result = response[1].read_body_bytes().split('\x00')
1153
def set_revision_history(self, rev_history):
1154
# Send just the tip revision of the history; the server will generate
1155
# the full history from that. If the revision doesn't exist in this
1156
# branch, NoSuchRevision will be raised.
1157
path = self.bzrdir._path_for_remote_call(self._client)
1158
if rev_history == []:
1161
rev_id = rev_history[-1]
1162
self._clear_cached_state()
1163
response = self._client.call('Branch.set_last_revision',
1164
path, self._lock_token, self._repo_lock_token, rev_id)
1165
if response[0] == 'NoSuchRevision':
1166
raise NoSuchRevision(self, rev_id)
1168
assert response == ('ok',), (
1169
'unexpected response code %r' % (response,))
1170
self._cache_revision_history(rev_history)
1172
def get_parent(self):
1174
return self._real_branch.get_parent()
1176
def set_parent(self, url):
1178
return self._real_branch.set_parent(url)
1180
def get_config(self):
1181
return RemoteBranchConfig(self)
1183
def sprout(self, to_bzrdir, revision_id=None):
1184
# Like Branch.sprout, except that it sprouts a branch in the default
1185
# format, because RemoteBranches can't be created at arbitrary URLs.
1186
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1187
# to_bzrdir.create_branch...
1188
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1189
self.copy_content_into(result, revision_id=revision_id)
1190
result.set_parent(self.bzrdir.root_transport.base)
1194
def pull(self, source, overwrite=False, stop_revision=None,
1196
# FIXME: This asks the real branch to run the hooks, which means
1197
# they're called with the wrong target branch parameter.
1198
# The test suite specifically allows this at present but it should be
1199
# fixed. It should get a _override_hook_target branch,
1200
# as push does. -- mbp 20070405
1202
self._real_branch.pull(
1203
source, overwrite=overwrite, stop_revision=stop_revision,
1207
def push(self, target, overwrite=False, stop_revision=None):
1209
return self._real_branch.push(
1210
target, overwrite=overwrite, stop_revision=stop_revision,
1211
_override_hook_source_branch=self)
1213
def is_locked(self):
1214
return self._lock_count >= 1
1216
def set_last_revision_info(self, revno, revision_id):
1218
self._clear_cached_state()
1219
return self._real_branch.set_last_revision_info(revno, revision_id)
1221
def generate_revision_history(self, revision_id, last_rev=None,
1224
return self._real_branch.generate_revision_history(
1225
revision_id, last_rev=last_rev, other_branch=other_branch)
1230
return self._real_branch.tags
1232
def set_push_location(self, location):
1234
return self._real_branch.set_push_location(location)
1236
def update_revisions(self, other, stop_revision=None):
1238
return self._real_branch.update_revisions(
1239
other, stop_revision=stop_revision)
1242
class RemoteBranchConfig(BranchConfig):
1245
self.branch._ensure_real()
1246
return self.branch._real_branch.get_config().username()
1248
def _get_branch_data_config(self):
1249
self.branch._ensure_real()
1250
if self._branch_data_config is None:
1251
self._branch_data_config = TreeConfig(self.branch._real_branch)
1252
return self._branch_data_config
1255
def _extract_tar(tar, to_dir):
1256
"""Extract all the contents of a tarfile object.
1258
A replacement for extractall, which is not present in python2.4
1261
tar.extract(tarinfo, to_dir)