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
self._reconcile_does_inventory_gc = True
263
self.base = self.bzrdir.transport.base
266
return "%s(%s)" % (self.__class__.__name__, self.base)
270
def abort_write_group(self):
271
"""Complete a write group on the decorated repository.
273
Smart methods peform operations in a single step so this api
274
is not really applicable except as a compatibility thunk
275
for older plugins that don't use e.g. the CommitBuilder
279
return self._real_repository.abort_write_group()
281
def commit_write_group(self):
282
"""Complete a write group on the decorated repository.
284
Smart methods peform operations in a single step so this api
285
is not really applicable except as a compatibility thunk
286
for older plugins that don't use e.g. the CommitBuilder
290
return self._real_repository.commit_write_group()
292
def _ensure_real(self):
293
"""Ensure that there is a _real_repository set.
295
Used before calls to self._real_repository.
297
if not self._real_repository:
298
self.bzrdir._ensure_real()
299
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
300
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
302
def get_revision_graph(self, revision_id=None):
303
"""See Repository.get_revision_graph()."""
304
if revision_id is None:
306
elif revision_id == NULL_REVISION:
309
path = self.bzrdir._path_for_remote_call(self._client)
310
assert type(revision_id) is str
311
response = self._client.call_expecting_body(
312
'Repository.get_revision_graph', path, revision_id)
313
if response[0][0] not in ['ok', 'nosuchrevision']:
314
raise errors.UnexpectedSmartServerResponse(response[0])
315
if response[0][0] == 'ok':
316
coded = response[1].read_body_bytes()
318
# no revisions in this repository!
320
lines = coded.split('\n')
323
d = tuple(line.split())
324
revision_graph[d[0]] = d[1:]
326
return revision_graph
328
response_body = response[1].read_body_bytes()
329
assert response_body == ''
330
raise NoSuchRevision(self, revision_id)
332
def has_revision(self, revision_id):
333
"""See Repository.has_revision()."""
334
if revision_id is None:
335
# The null revision is always present.
337
path = self.bzrdir._path_for_remote_call(self._client)
338
response = self._client.call('Repository.has_revision', path, revision_id)
339
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
340
return response[0] == 'yes'
342
def has_same_location(self, other):
343
return (self.__class__ == other.__class__ and
344
self.bzrdir.transport.base == other.bzrdir.transport.base)
346
def get_graph(self, other_repository=None):
347
"""Return the graph for this repository format"""
348
return self._real_repository.get_graph(other_repository)
350
def gather_stats(self, revid=None, committers=None):
351
"""See Repository.gather_stats()."""
352
path = self.bzrdir._path_for_remote_call(self._client)
353
if revid in (None, NULL_REVISION):
357
if committers is None or not committers:
358
fmt_committers = 'no'
360
fmt_committers = 'yes'
361
response = self._client.call_expecting_body(
362
'Repository.gather_stats', path, fmt_revid, fmt_committers)
363
assert response[0][0] == 'ok', \
364
'unexpected response code %s' % (response[0],)
366
body = response[1].read_body_bytes()
368
for line in body.split('\n'):
371
key, val_text = line.split(':')
372
if key in ('revisions', 'size', 'committers'):
373
result[key] = int(val_text)
374
elif key in ('firstrev', 'latestrev'):
375
values = val_text.split(' ')[1:]
376
result[key] = (float(values[0]), long(values[1]))
380
def get_physical_lock_status(self):
381
"""See Repository.get_physical_lock_status()."""
384
def is_in_write_group(self):
385
"""Return True if there is an open write group.
387
write groups are only applicable locally for the smart server..
389
if self._real_repository:
390
return self._real_repository.is_in_write_group()
393
return self._lock_count >= 1
396
"""See Repository.is_shared()."""
397
path = self.bzrdir._path_for_remote_call(self._client)
398
response = self._client.call('Repository.is_shared', path)
399
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
400
return response[0] == 'yes'
403
# wrong eventually - want a local lock cache context
404
if not self._lock_mode:
405
self._lock_mode = 'r'
407
if self._real_repository is not None:
408
self._real_repository.lock_read()
410
self._lock_count += 1
412
def _remote_lock_write(self, token):
413
path = self.bzrdir._path_for_remote_call(self._client)
416
response = self._client.call('Repository.lock_write', path, token)
417
if response[0] == 'ok':
420
elif response[0] == 'LockContention':
421
raise errors.LockContention('(remote lock)')
422
elif response[0] == 'UnlockableTransport':
423
raise errors.UnlockableTransport(self.bzrdir.root_transport)
424
elif response[0] == 'LockFailed':
425
raise errors.LockFailed(response[1], response[2])
427
raise errors.UnexpectedSmartServerResponse(response)
429
def lock_write(self, token=None):
430
if not self._lock_mode:
431
self._lock_token = self._remote_lock_write(token)
432
assert self._lock_token, 'Remote server did not return a token!'
433
if self._real_repository is not None:
434
self._real_repository.lock_write(token=self._lock_token)
435
if token is not None:
436
self._leave_lock = True
438
self._leave_lock = False
439
self._lock_mode = 'w'
441
elif self._lock_mode == 'r':
442
raise errors.ReadOnlyError(self)
444
self._lock_count += 1
445
return self._lock_token
447
def leave_lock_in_place(self):
448
self._leave_lock = True
450
def dont_leave_lock_in_place(self):
451
self._leave_lock = False
453
def _set_real_repository(self, repository):
454
"""Set the _real_repository for this repository.
456
:param repository: The repository to fallback to for non-hpss
457
implemented operations.
459
assert not isinstance(repository, RemoteRepository)
460
self._real_repository = repository
461
if self._lock_mode == 'w':
462
# if we are already locked, the real repository must be able to
463
# acquire the lock with our token.
464
self._real_repository.lock_write(self._lock_token)
465
elif self._lock_mode == 'r':
466
self._real_repository.lock_read()
468
def start_write_group(self):
469
"""Start a write group on the decorated repository.
471
Smart methods peform operations in a single step so this api
472
is not really applicable except as a compatibility thunk
473
for older plugins that don't use e.g. the CommitBuilder
477
return self._real_repository.start_write_group()
479
def _unlock(self, token):
480
path = self.bzrdir._path_for_remote_call(self._client)
481
response = self._client.call('Repository.unlock', path, token)
482
if response == ('ok',):
484
elif response[0] == 'TokenMismatch':
485
raise errors.TokenMismatch(token, '(remote token)')
487
raise errors.UnexpectedSmartServerResponse(response)
490
if self._lock_count == 1 and self._lock_mode == 'w':
491
# don't unlock if inside a write group.
492
if self.is_in_write_group():
493
raise errors.BzrError(
494
'Must end write groups before releasing write locks.')
495
self._lock_count -= 1
496
if not self._lock_count:
497
mode = self._lock_mode
498
self._lock_mode = None
499
if self._real_repository is not None:
500
self._real_repository.unlock()
502
# Only write-locked repositories need to make a remote method
503
# call to perfom the unlock.
505
assert self._lock_token, 'Locked, but no token!'
506
token = self._lock_token
507
self._lock_token = None
508
if not self._leave_lock:
511
def break_lock(self):
512
# should hand off to the network
514
return self._real_repository.break_lock()
516
def _get_tarball(self, compression):
517
"""Return a TemporaryFile containing a repository tarball.
519
Returns None if the server does not support sending tarballs.
522
path = self.bzrdir._path_for_remote_call(self._client)
523
response, protocol = self._client.call_expecting_body(
524
'Repository.tarball', path, compression)
525
if response[0] == 'ok':
526
# Extract the tarball and return it
527
t = tempfile.NamedTemporaryFile()
528
# TODO: rpc layer should read directly into it...
529
t.write(protocol.read_body_bytes())
532
if (response == ('error', "Generic bzr smart protocol error: "
533
"bad request 'Repository.tarball'") or
534
response == ('error', "Generic bzr smart protocol error: "
535
"bad request u'Repository.tarball'")):
536
protocol.cancel_read_body()
538
raise errors.UnexpectedSmartServerResponse(response)
540
def sprout(self, to_bzrdir, revision_id=None):
541
# TODO: Option to control what format is created?
542
dest_repo = to_bzrdir.create_repository()
543
dest_repo.fetch(self, revision_id=revision_id)
546
### These methods are just thin shims to the VFS object for now.
548
def revision_tree(self, revision_id):
550
return self._real_repository.revision_tree(revision_id)
552
def get_serializer_format(self):
554
return self._real_repository.get_serializer_format()
556
def get_commit_builder(self, branch, parents, config, timestamp=None,
557
timezone=None, committer=None, revprops=None,
559
# FIXME: It ought to be possible to call this without immediately
560
# triggering _ensure_real. For now it's the easiest thing to do.
562
builder = self._real_repository.get_commit_builder(branch, parents,
563
config, timestamp=timestamp, timezone=timezone,
564
committer=committer, revprops=revprops, revision_id=revision_id)
565
# Make the builder use this RemoteRepository rather than the real one.
566
builder.repository = self
570
def add_inventory(self, revid, inv, parents):
572
return self._real_repository.add_inventory(revid, inv, parents)
575
def add_revision(self, rev_id, rev, inv=None, config=None):
577
return self._real_repository.add_revision(
578
rev_id, rev, inv=inv, config=config)
581
def get_inventory(self, revision_id):
583
return self._real_repository.get_inventory(revision_id)
586
def get_revision(self, revision_id):
588
return self._real_repository.get_revision(revision_id)
591
def weave_store(self):
593
return self._real_repository.weave_store
595
def get_transaction(self):
597
return self._real_repository.get_transaction()
600
def clone(self, a_bzrdir, revision_id=None):
602
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
604
def make_working_trees(self):
605
"""RemoteRepositories never create working trees by default."""
608
def fetch(self, source, revision_id=None, pb=None):
609
if self.has_same_location(source):
610
# check that last_revision is in 'from' and then return a
612
if (revision_id is not None and
613
not _mod_revision.is_null(revision_id)):
614
self.get_revision(revision_id)
617
return self._real_repository.fetch(
618
source, revision_id=revision_id, pb=pb)
620
def create_bundle(self, target, base, fileobj, format=None):
622
self._real_repository.create_bundle(target, base, fileobj, format)
625
def control_weaves(self):
627
return self._real_repository.control_weaves
630
def get_ancestry(self, revision_id, topo_sorted=True):
632
return self._real_repository.get_ancestry(revision_id, topo_sorted)
635
def get_inventory_weave(self):
637
return self._real_repository.get_inventory_weave()
639
def fileids_altered_by_revision_ids(self, revision_ids):
641
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
643
def iter_files_bytes(self, desired_files):
644
"""See Repository.iter_file_bytes.
647
return self._real_repository.iter_files_bytes(desired_files)
650
def get_signature_text(self, revision_id):
652
return self._real_repository.get_signature_text(revision_id)
655
def get_revision_graph_with_ghosts(self, revision_ids=None):
657
return self._real_repository.get_revision_graph_with_ghosts(
658
revision_ids=revision_ids)
661
def get_inventory_xml(self, revision_id):
663
return self._real_repository.get_inventory_xml(revision_id)
665
def deserialise_inventory(self, revision_id, xml):
667
return self._real_repository.deserialise_inventory(revision_id, xml)
669
def reconcile(self, other=None, thorough=False):
671
return self._real_repository.reconcile(other=other, thorough=thorough)
673
def all_revision_ids(self):
675
return self._real_repository.all_revision_ids()
678
def get_deltas_for_revisions(self, revisions):
680
return self._real_repository.get_deltas_for_revisions(revisions)
683
def get_revision_delta(self, revision_id):
685
return self._real_repository.get_revision_delta(revision_id)
688
def revision_trees(self, revision_ids):
690
return self._real_repository.revision_trees(revision_ids)
693
def get_revision_reconcile(self, revision_id):
695
return self._real_repository.get_revision_reconcile(revision_id)
698
def check(self, revision_ids):
700
return self._real_repository.check(revision_ids)
702
def copy_content_into(self, destination, revision_id=None):
704
return self._real_repository.copy_content_into(
705
destination, revision_id=revision_id)
707
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
708
# get a tarball of the remote repository, and copy from that into the
710
from bzrlib import osutils
713
from StringIO import StringIO
714
# TODO: Maybe a progress bar while streaming the tarball?
715
note("Copying repository content as tarball...")
716
tar_file = self._get_tarball('bz2')
719
destination = to_bzrdir.create_repository()
721
tar = tarfile.open('repository', fileobj=tar_file,
723
tmpdir = tempfile.mkdtemp()
725
_extract_tar(tar, tmpdir)
726
tmp_bzrdir = BzrDir.open(tmpdir)
727
tmp_repo = tmp_bzrdir.open_repository()
728
tmp_repo.copy_content_into(destination, revision_id)
730
osutils.rmtree(tmpdir)
734
# TODO: Suggestion from john: using external tar is much faster than
735
# python's tarfile library, but it may not work on windows.
739
"""Compress the data within the repository.
741
This is not currently implemented within the smart server.
744
return self._real_repository.pack()
746
def set_make_working_trees(self, new_value):
747
raise NotImplementedError(self.set_make_working_trees)
750
def sign_revision(self, revision_id, gpg_strategy):
752
return self._real_repository.sign_revision(revision_id, gpg_strategy)
755
def get_revisions(self, revision_ids):
757
return self._real_repository.get_revisions(revision_ids)
759
def supports_rich_root(self):
761
return self._real_repository.supports_rich_root()
763
def iter_reverse_revision_history(self, revision_id):
765
return self._real_repository.iter_reverse_revision_history(revision_id)
768
def _serializer(self):
770
return self._real_repository._serializer
772
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
774
return self._real_repository.store_revision_signature(
775
gpg_strategy, plaintext, revision_id)
777
def has_signature_for_revision_id(self, revision_id):
779
return self._real_repository.has_signature_for_revision_id(revision_id)
781
def get_data_stream(self, revision_ids):
782
path = self.bzrdir._path_for_remote_call(self._client)
783
response, protocol = self._client.call_expecting_body(
784
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
785
if response == ('ok',):
786
return self._deserialise_stream(protocol)
787
elif (response == ('error', "Generic bzr smart protocol error: "
788
"bad request 'Repository.stream_knit_data_for_revisions'") or
789
response == ('error', "Generic bzr smart protocol error: "
790
"bad request u'Repository.stream_knit_data_for_revisions'")):
791
protocol.cancel_read_body()
793
return self._real_repository.get_data_stream(revision_ids)
795
raise errors.UnexpectedSmartServerResponse(response)
797
def _deserialise_stream(self, protocol):
798
buffer = StringIO(protocol.read_body_bytes())
799
reader = ContainerReader(buffer)
800
for record_names, read_bytes in reader.iter_records():
802
# These records should have only one name, and that name
803
# should be a one-element tuple.
804
[name_tuple] = record_names
806
raise errors.SmartProtocolError(
807
'Repository data stream had invalid record name %r'
809
yield name_tuple, read_bytes(None)
811
def insert_data_stream(self, stream):
813
self._real_repository.insert_data_stream(stream)
815
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
817
return self._real_repository.item_keys_introduced_by(revision_ids,
821
class RemoteBranchLockableFiles(LockableFiles):
822
"""A 'LockableFiles' implementation that talks to a smart server.
824
This is not a public interface class.
827
def __init__(self, bzrdir, _client):
829
self._client = _client
830
self._need_find_modes = True
831
LockableFiles.__init__(
832
self, bzrdir.get_branch_transport(None),
833
'lock', lockdir.LockDir)
835
def _find_modes(self):
836
# RemoteBranches don't let the client set the mode of control files.
837
self._dir_mode = None
838
self._file_mode = None
841
"""'get' a remote path as per the LockableFiles interface.
843
:param path: the file to 'get'. If this is 'branch.conf', we do not
844
just retrieve a file, instead we ask the smart server to generate
845
a configuration for us - which is retrieved as an INI file.
847
if path == 'branch.conf':
848
path = self.bzrdir._path_for_remote_call(self._client)
849
response = self._client.call_expecting_body(
850
'Branch.get_config_file', path)
851
assert response[0][0] == 'ok', \
852
'unexpected response code %s' % (response[0],)
853
return StringIO(response[1].read_body_bytes())
856
return LockableFiles.get(self, path)
859
class RemoteBranchFormat(branch.BranchFormat):
861
def __eq__(self, other):
862
return (isinstance(other, RemoteBranchFormat) and
863
self.__dict__ == other.__dict__)
865
def get_format_description(self):
866
return 'Remote BZR Branch'
868
def get_format_string(self):
869
return 'Remote BZR Branch'
871
def open(self, a_bzrdir):
872
assert isinstance(a_bzrdir, RemoteBzrDir)
873
return a_bzrdir.open_branch()
875
def initialize(self, a_bzrdir):
876
assert isinstance(a_bzrdir, RemoteBzrDir)
877
return a_bzrdir.create_branch()
879
def supports_tags(self):
880
# Remote branches might support tags, but we won't know until we
881
# access the real remote branch.
885
class RemoteBranch(branch.Branch):
886
"""Branch stored on a server accessed by HPSS RPC.
888
At the moment most operations are mapped down to simple file operations.
891
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
893
"""Create a RemoteBranch instance.
895
:param real_branch: An optional local implementation of the branch
896
format, usually accessing the data via the VFS.
897
:param _client: Private parameter for testing.
899
# We intentionally don't call the parent class's __init__, because it
900
# will try to assign to self.tags, which is a property in this subclass.
901
# And the parent's __init__ doesn't do much anyway.
902
self._revision_history_cache = None
903
self.bzrdir = remote_bzrdir
904
if _client is not None:
905
self._client = _client
907
self._client = client._SmartClient(self.bzrdir._shared_medium)
908
self.repository = remote_repository
909
if real_branch is not None:
910
self._real_branch = real_branch
911
# Give the remote repository the matching real repo.
912
real_repo = self._real_branch.repository
913
if isinstance(real_repo, RemoteRepository):
914
real_repo._ensure_real()
915
real_repo = real_repo._real_repository
916
self.repository._set_real_repository(real_repo)
917
# Give the branch the remote repository to let fast-pathing happen.
918
self._real_branch.repository = self.repository
920
self._real_branch = None
921
# Fill out expected attributes of branch for bzrlib api users.
922
self._format = RemoteBranchFormat()
923
self.base = self.bzrdir.root_transport.base
924
self._control_files = None
925
self._lock_mode = None
926
self._lock_token = None
928
self._leave_lock = False
931
return "%s(%s)" % (self.__class__.__name__, self.base)
935
def _ensure_real(self):
936
"""Ensure that there is a _real_branch set.
938
Used before calls to self._real_branch.
940
if not self._real_branch:
941
assert vfs.vfs_enabled()
942
self.bzrdir._ensure_real()
943
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
944
# Give the remote repository the matching real repo.
945
real_repo = self._real_branch.repository
946
if isinstance(real_repo, RemoteRepository):
947
real_repo._ensure_real()
948
real_repo = real_repo._real_repository
949
self.repository._set_real_repository(real_repo)
950
# Give the branch the remote repository to let fast-pathing happen.
951
self._real_branch.repository = self.repository
952
# XXX: deal with _lock_mode == 'w'
953
if self._lock_mode == 'r':
954
self._real_branch.lock_read()
957
def control_files(self):
958
# Defer actually creating RemoteBranchLockableFiles until its needed,
959
# because it triggers an _ensure_real that we otherwise might not need.
960
if self._control_files is None:
961
self._control_files = RemoteBranchLockableFiles(
962
self.bzrdir, self._client)
963
return self._control_files
965
def _get_checkout_format(self):
967
return self._real_branch._get_checkout_format()
969
def get_physical_lock_status(self):
970
"""See Branch.get_physical_lock_status()."""
971
# should be an API call to the server, as branches must be lockable.
973
return self._real_branch.get_physical_lock_status()
976
if not self._lock_mode:
977
self._lock_mode = 'r'
979
if self._real_branch is not None:
980
self._real_branch.lock_read()
982
self._lock_count += 1
984
def _remote_lock_write(self, token):
986
branch_token = repo_token = ''
989
repo_token = self.repository.lock_write()
990
self.repository.unlock()
991
path = self.bzrdir._path_for_remote_call(self._client)
992
response = self._client.call('Branch.lock_write', path, branch_token,
994
if response[0] == 'ok':
995
ok, branch_token, repo_token = response
996
return branch_token, repo_token
997
elif response[0] == 'LockContention':
998
raise errors.LockContention('(remote lock)')
999
elif response[0] == 'TokenMismatch':
1000
raise errors.TokenMismatch(token, '(remote token)')
1001
elif response[0] == 'UnlockableTransport':
1002
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1003
elif response[0] == 'ReadOnlyError':
1004
raise errors.ReadOnlyError(self)
1005
elif response[0] == 'LockFailed':
1006
raise errors.LockFailed(response[1], response[2])
1008
raise errors.UnexpectedSmartServerResponse(response)
1010
def lock_write(self, token=None):
1011
if not self._lock_mode:
1012
remote_tokens = self._remote_lock_write(token)
1013
self._lock_token, self._repo_lock_token = remote_tokens
1014
assert self._lock_token, 'Remote server did not return a token!'
1015
# TODO: We really, really, really don't want to call _ensure_real
1016
# here, but it's the easiest way to ensure coherency between the
1017
# state of the RemoteBranch and RemoteRepository objects and the
1018
# physical locks. If we don't materialise the real objects here,
1019
# then getting everything in the right state later is complex, so
1020
# for now we just do it the lazy way.
1021
# -- Andrew Bennetts, 2007-02-22.
1023
if self._real_branch is not None:
1024
self._real_branch.repository.lock_write(
1025
token=self._repo_lock_token)
1027
self._real_branch.lock_write(token=self._lock_token)
1029
self._real_branch.repository.unlock()
1030
if token is not None:
1031
self._leave_lock = True
1033
# XXX: this case seems to be unreachable; token cannot be None.
1034
self._leave_lock = False
1035
self._lock_mode = 'w'
1036
self._lock_count = 1
1037
elif self._lock_mode == 'r':
1038
raise errors.ReadOnlyTransaction
1040
if token is not None:
1041
# A token was given to lock_write, and we're relocking, so check
1042
# that the given token actually matches the one we already have.
1043
if token != self._lock_token:
1044
raise errors.TokenMismatch(token, self._lock_token)
1045
self._lock_count += 1
1046
return self._lock_token
1048
def _unlock(self, branch_token, repo_token):
1049
path = self.bzrdir._path_for_remote_call(self._client)
1050
response = self._client.call('Branch.unlock', path, branch_token,
1052
if response == ('ok',):
1054
elif response[0] == 'TokenMismatch':
1055
raise errors.TokenMismatch(
1056
str((branch_token, repo_token)), '(remote tokens)')
1058
raise errors.UnexpectedSmartServerResponse(response)
1061
self._lock_count -= 1
1062
if not self._lock_count:
1063
self._clear_cached_state()
1064
mode = self._lock_mode
1065
self._lock_mode = None
1066
if self._real_branch is not None:
1067
if not self._leave_lock:
1068
# If this RemoteBranch will remove the physical lock for the
1069
# repository, make sure the _real_branch doesn't do it
1070
# first. (Because the _real_branch's repository is set to
1071
# be the RemoteRepository.)
1072
self._real_branch.repository.leave_lock_in_place()
1073
self._real_branch.unlock()
1075
# Only write-locked branched need to make a remote method call
1076
# to perfom the unlock.
1078
assert self._lock_token, 'Locked, but no token!'
1079
branch_token = self._lock_token
1080
repo_token = self._repo_lock_token
1081
self._lock_token = None
1082
self._repo_lock_token = None
1083
if not self._leave_lock:
1084
self._unlock(branch_token, repo_token)
1086
def break_lock(self):
1088
return self._real_branch.break_lock()
1090
def leave_lock_in_place(self):
1091
self._leave_lock = True
1093
def dont_leave_lock_in_place(self):
1094
self._leave_lock = False
1096
def last_revision_info(self):
1097
"""See Branch.last_revision_info()."""
1098
path = self.bzrdir._path_for_remote_call(self._client)
1099
response = self._client.call('Branch.last_revision_info', path)
1100
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1101
revno = int(response[1])
1102
last_revision = response[2]
1103
return (revno, last_revision)
1105
def _gen_revision_history(self):
1106
"""See Branch._gen_revision_history()."""
1107
path = self.bzrdir._path_for_remote_call(self._client)
1108
response = self._client.call_expecting_body(
1109
'Branch.revision_history', path)
1110
assert response[0][0] == 'ok', ('unexpected response code %s'
1112
result = response[1].read_body_bytes().split('\x00')
1118
def set_revision_history(self, rev_history):
1119
# Send just the tip revision of the history; the server will generate
1120
# the full history from that. If the revision doesn't exist in this
1121
# branch, NoSuchRevision will be raised.
1122
path = self.bzrdir._path_for_remote_call(self._client)
1123
if rev_history == []:
1126
rev_id = rev_history[-1]
1127
self._clear_cached_state()
1128
response = self._client.call('Branch.set_last_revision',
1129
path, self._lock_token, self._repo_lock_token, rev_id)
1130
if response[0] == 'NoSuchRevision':
1131
raise NoSuchRevision(self, rev_id)
1133
assert response == ('ok',), (
1134
'unexpected response code %r' % (response,))
1135
self._cache_revision_history(rev_history)
1137
def get_parent(self):
1139
return self._real_branch.get_parent()
1141
def set_parent(self, url):
1143
return self._real_branch.set_parent(url)
1145
def get_config(self):
1146
return RemoteBranchConfig(self)
1148
def sprout(self, to_bzrdir, revision_id=None):
1149
# Like Branch.sprout, except that it sprouts a branch in the default
1150
# format, because RemoteBranches can't be created at arbitrary URLs.
1151
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1152
# to_bzrdir.create_branch...
1153
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1154
self.copy_content_into(result, revision_id=revision_id)
1155
result.set_parent(self.bzrdir.root_transport.base)
1159
def pull(self, source, overwrite=False, stop_revision=None,
1161
# FIXME: This asks the real branch to run the hooks, which means
1162
# they're called with the wrong target branch parameter.
1163
# The test suite specifically allows this at present but it should be
1164
# fixed. It should get a _override_hook_target branch,
1165
# as push does. -- mbp 20070405
1167
self._real_branch.pull(
1168
source, overwrite=overwrite, stop_revision=stop_revision,
1172
def push(self, target, overwrite=False, stop_revision=None):
1174
return self._real_branch.push(
1175
target, overwrite=overwrite, stop_revision=stop_revision,
1176
_override_hook_source_branch=self)
1178
def is_locked(self):
1179
return self._lock_count >= 1
1181
def set_last_revision_info(self, revno, revision_id):
1183
self._clear_cached_state()
1184
return self._real_branch.set_last_revision_info(revno, revision_id)
1186
def generate_revision_history(self, revision_id, last_rev=None,
1189
return self._real_branch.generate_revision_history(
1190
revision_id, last_rev=last_rev, other_branch=other_branch)
1195
return self._real_branch.tags
1197
def set_push_location(self, location):
1199
return self._real_branch.set_push_location(location)
1201
def update_revisions(self, other, stop_revision=None):
1203
return self._real_branch.update_revisions(
1204
other, stop_revision=stop_revision)
1207
class RemoteBranchConfig(BranchConfig):
1210
self.branch._ensure_real()
1211
return self.branch._real_branch.get_config().username()
1213
def _get_branch_data_config(self):
1214
self.branch._ensure_real()
1215
if self._branch_data_config is None:
1216
self._branch_data_config = TreeConfig(self.branch._real_branch)
1217
return self._branch_data_config
1220
def _extract_tar(tar, to_dir):
1221
"""Extract all the contents of a tarfile object.
1223
A replacement for extractall, which is not present in python2.4
1226
tar.extract(tarinfo, to_dir)