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.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
38
# Note: RemoteBzrDirFormat is in bzrdir.py
40
class RemoteBzrDir(BzrDir):
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
43
def __init__(self, transport, _client=None):
44
"""Construct a RemoteBzrDir.
46
:param _client: Private parameter for testing. Disables probing and the
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
50
# this object holds a delegated bzrdir that uses file-level operations
51
# to talk to the other side
52
self._real_bzrdir = None
55
self._shared_medium = transport.get_shared_medium()
56
self._client = client._SmartClient(self._shared_medium)
58
self._client = _client
59
self._shared_medium = None
62
path = self._path_for_remote_call(self._client)
63
response = self._client.call('BzrDir.open', path)
64
if response not in [('yes',), ('no',)]:
65
raise errors.UnexpectedSmartServerResponse(response)
66
if response == ('no',):
67
raise errors.NotBranchError(path=transport.base)
69
def _ensure_real(self):
70
"""Ensure that there is a _real_bzrdir set.
72
Used before calls to self._real_bzrdir.
74
if not self._real_bzrdir:
75
self._real_bzrdir = BzrDir.open_from_transport(
76
self.root_transport, _server_formats=False)
78
def create_repository(self, shared=False):
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
83
def create_branch(self):
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
88
def create_workingtree(self, revision_id=None):
89
raise errors.NotLocalUrl(self.transport.base)
91
def find_branch_format(self):
92
"""Find the branch 'format' for this bzrdir.
94
This might be a synthetic object for e.g. RemoteBranch and SVN.
96
b = self.open_branch()
99
def get_branch_reference(self):
100
"""See BzrDir.get_branch_reference()."""
101
path = self._path_for_remote_call(self._client)
102
response = self._client.call('BzrDir.open_branch', path)
103
if response[0] == 'ok':
104
if response[1] == '':
105
# branch at this location.
108
# a branch reference, use the existing BranchReference logic.
110
elif response == ('nobranch',):
111
raise errors.NotBranchError(path=self.root_transport.base)
113
raise errors.UnexpectedSmartServerResponse(response)
115
def open_branch(self, _unsupported=False):
116
assert _unsupported == False, 'unsupported flag support not implemented yet.'
117
reference_url = self.get_branch_reference()
118
if reference_url is None:
119
# branch at this location.
120
return RemoteBranch(self, self.find_repository())
122
# a branch reference, use the existing BranchReference logic.
123
format = BranchReferenceFormat()
124
return format.open(self, _found=True, location=reference_url)
126
def open_repository(self):
127
path = self._path_for_remote_call(self._client)
128
response = self._client.call('BzrDir.find_repository', path)
129
assert response[0] in ('ok', 'norepository'), \
130
'unexpected response code %s' % (response,)
131
if response[0] == 'norepository':
132
raise errors.NoRepositoryPresent(self)
133
assert len(response) == 4, 'incorrect response length %s' % (response,)
134
if response[1] == '':
135
format = RemoteRepositoryFormat()
136
format.rich_root_data = (response[2] == 'yes')
137
format.supports_tree_reference = (response[3] == 'yes')
138
return RemoteRepository(self, format)
140
raise errors.NoRepositoryPresent(self)
142
def open_workingtree(self, recommend_upgrade=True):
144
if self._real_bzrdir.has_workingtree():
145
raise errors.NotLocalUrl(self.root_transport)
147
raise errors.NoWorkingTree(self.root_transport.base)
149
def _path_for_remote_call(self, client):
150
"""Return the path to be used for this bzrdir in a remote call."""
151
return client.remote_path_from_transport(self.root_transport)
153
def get_branch_transport(self, branch_format):
155
return self._real_bzrdir.get_branch_transport(branch_format)
157
def get_repository_transport(self, repository_format):
159
return self._real_bzrdir.get_repository_transport(repository_format)
161
def get_workingtree_transport(self, workingtree_format):
163
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
165
def can_convert_format(self):
166
"""Upgrading of remote bzrdirs is not supported yet."""
169
def needs_format_conversion(self, format=None):
170
"""Upgrading of remote bzrdirs is not supported yet."""
173
def clone(self, url, revision_id=None, force_new_repo=False):
175
return self._real_bzrdir.clone(url, revision_id=revision_id,
176
force_new_repo=force_new_repo)
179
class RemoteRepositoryFormat(repository.RepositoryFormat):
180
"""Format for repositories accessed over a _SmartClient.
182
Instances of this repository are represented by RemoteRepository
185
The RemoteRepositoryFormat is parameterised during construction
186
to reflect the capabilities of the real, remote format. Specifically
187
the attributes rich_root_data and supports_tree_reference are set
188
on a per instance basis, and are not set (and should not be) at
192
_matchingbzrdir = RemoteBzrDirFormat
194
def initialize(self, a_bzrdir, shared=False):
195
assert isinstance(a_bzrdir, RemoteBzrDir), \
196
'%r is not a RemoteBzrDir' % (a_bzrdir,)
197
return a_bzrdir.create_repository(shared=shared)
199
def open(self, a_bzrdir):
200
assert isinstance(a_bzrdir, RemoteBzrDir)
201
return a_bzrdir.open_repository()
203
def get_format_description(self):
204
return 'bzr remote repository'
206
def __eq__(self, other):
207
return self.__class__ == other.__class__
209
def check_conversion_target(self, target_format):
210
if self.rich_root_data and not target_format.rich_root_data:
211
raise errors.BadConversionTarget(
212
'Does not support rich root data.', target_format)
213
if (self.supports_tree_reference and
214
not getattr(target_format, 'supports_tree_reference', False)):
215
raise errors.BadConversionTarget(
216
'Does not support nested trees', target_format)
219
class RemoteRepository(object):
220
"""Repository accessed over rpc.
222
For the moment most operations are performed using local transport-backed
226
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
227
"""Create a RemoteRepository instance.
229
:param remote_bzrdir: The bzrdir hosting this repository.
230
:param format: The RemoteFormat object to use.
231
:param real_repository: If not None, a local implementation of the
232
repository logic for the repository, usually accessing the data
234
:param _client: Private testing parameter - override the smart client
235
to be used by the repository.
238
self._real_repository = real_repository
240
self._real_repository = None
241
self.bzrdir = remote_bzrdir
243
self._client = client._SmartClient(self.bzrdir._shared_medium)
245
self._client = _client
246
self._format = format
247
self._lock_mode = None
248
self._lock_token = None
250
self._leave_lock = False
252
def abort_write_group(self):
253
"""Complete a write group on the decorated repository.
255
Smart methods peform operations in a single step so this api
256
is not really applicable except as a compatibility thunk
257
for older plugins that don't use e.g. the CommitBuilder
261
return self._real_repository.abort_write_group()
263
def commit_write_group(self):
264
"""Complete a write group on the decorated repository.
266
Smart methods peform operations in a single step so this api
267
is not really applicable except as a compatibility thunk
268
for older plugins that don't use e.g. the CommitBuilder
272
return self._real_repository.commit_write_group()
274
def _ensure_real(self):
275
"""Ensure that there is a _real_repository set.
277
Used before calls to self._real_repository.
279
if not self._real_repository:
280
self.bzrdir._ensure_real()
281
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
282
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
284
def get_revision_graph(self, revision_id=None):
285
"""See Repository.get_revision_graph()."""
286
if revision_id is None:
288
elif revision_id == NULL_REVISION:
291
path = self.bzrdir._path_for_remote_call(self._client)
292
assert type(revision_id) is str
293
response = self._client.call_expecting_body(
294
'Repository.get_revision_graph', path, revision_id)
295
if response[0][0] not in ['ok', 'nosuchrevision']:
296
raise errors.UnexpectedSmartServerResponse(response[0])
297
if response[0][0] == 'ok':
298
coded = response[1].read_body_bytes()
300
# no revisions in this repository!
302
lines = coded.split('\n')
305
d = tuple(line.split())
306
revision_graph[d[0]] = d[1:]
308
return revision_graph
310
response_body = response[1].read_body_bytes()
311
assert response_body == ''
312
raise NoSuchRevision(self, revision_id)
314
def has_revision(self, revision_id):
315
"""See Repository.has_revision()."""
316
if revision_id is None:
317
# The null revision is always present.
319
path = self.bzrdir._path_for_remote_call(self._client)
320
response = self._client.call('Repository.has_revision', path, revision_id)
321
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
322
return response[0] == 'yes'
324
def has_same_location(self, other):
325
return (self.__class__ == other.__class__ and
326
self.bzrdir.transport.base == other.bzrdir.transport.base)
328
def get_graph(self, other_repository=None):
329
"""Return the graph for this repository format"""
330
return self._real_repository.get_graph(other_repository)
332
def gather_stats(self, revid=None, committers=None):
333
"""See Repository.gather_stats()."""
334
path = self.bzrdir._path_for_remote_call(self._client)
335
if revid in (None, NULL_REVISION):
339
if committers is None or not committers:
340
fmt_committers = 'no'
342
fmt_committers = 'yes'
343
response = self._client.call_expecting_body(
344
'Repository.gather_stats', path, fmt_revid, fmt_committers)
345
assert response[0][0] == 'ok', \
346
'unexpected response code %s' % (response[0],)
348
body = response[1].read_body_bytes()
350
for line in body.split('\n'):
353
key, val_text = line.split(':')
354
if key in ('revisions', 'size', 'committers'):
355
result[key] = int(val_text)
356
elif key in ('firstrev', 'latestrev'):
357
values = val_text.split(' ')[1:]
358
result[key] = (float(values[0]), long(values[1]))
362
def get_physical_lock_status(self):
363
"""See Repository.get_physical_lock_status()."""
366
def is_in_write_group(self):
367
"""Return True if there is an open write group.
369
write groups are only applicable locally for the smart server..
371
if self._real_repository:
372
return self._real_repository.is_in_write_group()
375
return self._lock_count >= 1
378
"""See Repository.is_shared()."""
379
path = self.bzrdir._path_for_remote_call(self._client)
380
response = self._client.call('Repository.is_shared', path)
381
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
382
return response[0] == 'yes'
385
# wrong eventually - want a local lock cache context
386
if not self._lock_mode:
387
self._lock_mode = 'r'
389
if self._real_repository is not None:
390
self._real_repository.lock_read()
392
self._lock_count += 1
394
def _remote_lock_write(self, token):
395
path = self.bzrdir._path_for_remote_call(self._client)
398
response = self._client.call('Repository.lock_write', path, token)
399
if response[0] == 'ok':
402
elif response[0] == 'LockContention':
403
raise errors.LockContention('(remote lock)')
404
elif response[0] == 'UnlockableTransport':
405
raise errors.UnlockableTransport(self.bzrdir.root_transport)
407
raise errors.UnexpectedSmartServerResponse(response)
409
def lock_write(self, token=None):
410
if not self._lock_mode:
411
self._lock_token = self._remote_lock_write(token)
412
assert self._lock_token, 'Remote server did not return a token!'
413
if self._real_repository is not None:
414
self._real_repository.lock_write(token=self._lock_token)
415
if token is not None:
416
self._leave_lock = True
418
self._leave_lock = False
419
self._lock_mode = 'w'
421
elif self._lock_mode == 'r':
422
raise errors.ReadOnlyError(self)
424
self._lock_count += 1
425
return self._lock_token
427
def leave_lock_in_place(self):
428
self._leave_lock = True
430
def dont_leave_lock_in_place(self):
431
self._leave_lock = False
433
def _set_real_repository(self, repository):
434
"""Set the _real_repository for this repository.
436
:param repository: The repository to fallback to for non-hpss
437
implemented operations.
439
assert not isinstance(repository, RemoteRepository)
440
self._real_repository = repository
441
if self._lock_mode == 'w':
442
# if we are already locked, the real repository must be able to
443
# acquire the lock with our token.
444
self._real_repository.lock_write(self._lock_token)
445
elif self._lock_mode == 'r':
446
self._real_repository.lock_read()
448
def start_write_group(self):
449
"""Start a write group on the decorated repository.
451
Smart methods peform operations in a single step so this api
452
is not really applicable except as a compatibility thunk
453
for older plugins that don't use e.g. the CommitBuilder
457
return self._real_repository.start_write_group()
459
def _unlock(self, token):
460
path = self.bzrdir._path_for_remote_call(self._client)
461
response = self._client.call('Repository.unlock', path, token)
462
if response == ('ok',):
464
elif response[0] == 'TokenMismatch':
465
raise errors.TokenMismatch(token, '(remote token)')
467
raise errors.UnexpectedSmartServerResponse(response)
470
if self._lock_count == 1 and self._lock_mode == 'w':
471
# don't unlock if inside a write group.
472
if self.is_in_write_group():
473
raise errors.BzrError(
474
'Must end write groups before releasing write locks.')
475
self._lock_count -= 1
476
if not self._lock_count:
477
mode = self._lock_mode
478
self._lock_mode = None
479
if self._real_repository is not None:
480
self._real_repository.unlock()
482
# Only write-locked repositories need to make a remote method
483
# call to perfom the unlock.
485
assert self._lock_token, 'Locked, but no token!'
486
token = self._lock_token
487
self._lock_token = None
488
if not self._leave_lock:
491
def break_lock(self):
492
# should hand off to the network
494
return self._real_repository.break_lock()
496
def _get_tarball(self, compression):
497
"""Return a TemporaryFile containing a repository tarball"""
499
path = self.bzrdir._path_for_remote_call(self._client)
500
response, protocol = self._client.call_expecting_body(
501
'Repository.tarball', path, compression)
502
assert response[0] in ('ok', 'failure'), \
503
'unexpected response code %s' % (response,)
504
if response[0] == 'ok':
505
# Extract the tarball and return it
506
t = tempfile.NamedTemporaryFile()
507
# TODO: rpc layer should read directly into it...
508
t.write(protocol.read_body_bytes())
512
raise errors.SmartServerError(error_code=response)
514
def sprout(self, to_bzrdir, revision_id=None):
515
# TODO: Option to control what format is created?
516
to_repo = to_bzrdir.create_repository()
517
self._copy_repository_tarball(to_repo, revision_id)
520
### These methods are just thin shims to the VFS object for now.
522
def revision_tree(self, revision_id):
524
return self._real_repository.revision_tree(revision_id)
526
def get_serializer_format(self):
528
return self._real_repository.get_serializer_format()
530
def get_commit_builder(self, branch, parents, config, timestamp=None,
531
timezone=None, committer=None, revprops=None,
533
# FIXME: It ought to be possible to call this without immediately
534
# triggering _ensure_real. For now it's the easiest thing to do.
536
builder = self._real_repository.get_commit_builder(branch, parents,
537
config, timestamp=timestamp, timezone=timezone,
538
committer=committer, revprops=revprops, revision_id=revision_id)
539
# Make the builder use this RemoteRepository rather than the real one.
540
builder.repository = self
544
def add_inventory(self, revid, inv, parents):
546
return self._real_repository.add_inventory(revid, inv, parents)
549
def add_revision(self, rev_id, rev, inv=None, config=None):
551
return self._real_repository.add_revision(
552
rev_id, rev, inv=inv, config=config)
555
def get_inventory(self, revision_id):
557
return self._real_repository.get_inventory(revision_id)
560
def get_revision(self, revision_id):
562
return self._real_repository.get_revision(revision_id)
565
def weave_store(self):
567
return self._real_repository.weave_store
569
def get_transaction(self):
571
return self._real_repository.get_transaction()
574
def clone(self, a_bzrdir, revision_id=None):
576
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
578
def make_working_trees(self):
579
"""RemoteRepositories never create working trees by default."""
582
def fetch(self, source, revision_id=None, pb=None):
584
return self._real_repository.fetch(
585
source, revision_id=revision_id, pb=pb)
587
def create_bundle(self, target, base, fileobj, format=None):
589
self._real_repository.create_bundle(target, base, fileobj, format)
592
def control_weaves(self):
594
return self._real_repository.control_weaves
597
def get_ancestry(self, revision_id, topo_sorted=True):
599
return self._real_repository.get_ancestry(revision_id, topo_sorted)
602
def get_inventory_weave(self):
604
return self._real_repository.get_inventory_weave()
606
def fileids_altered_by_revision_ids(self, revision_ids):
608
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
610
def iter_files_bytes(self, desired_files):
611
"""Provide file contents to the callable, as an iterator of bytes.
613
The order in which files will be extracted is unspecified.
614
The default implementation just does get_file().
615
:param desired_files: a list of (file_id, revision_id, callable_data)
619
return self._real_repository.iter_files_bytes(desired_files)
622
def get_signature_text(self, revision_id):
624
return self._real_repository.get_signature_text(revision_id)
627
def get_revision_graph_with_ghosts(self, revision_ids=None):
629
return self._real_repository.get_revision_graph_with_ghosts(
630
revision_ids=revision_ids)
633
def get_inventory_xml(self, revision_id):
635
return self._real_repository.get_inventory_xml(revision_id)
637
def deserialise_inventory(self, revision_id, xml):
639
return self._real_repository.deserialise_inventory(revision_id, xml)
641
def reconcile(self, other=None, thorough=False):
643
return self._real_repository.reconcile(other=other, thorough=thorough)
645
def all_revision_ids(self):
647
return self._real_repository.all_revision_ids()
650
def get_deltas_for_revisions(self, revisions):
652
return self._real_repository.get_deltas_for_revisions(revisions)
655
def get_revision_delta(self, revision_id):
657
return self._real_repository.get_revision_delta(revision_id)
660
def revision_trees(self, revision_ids):
662
return self._real_repository.revision_trees(revision_ids)
665
def get_revision_reconcile(self, revision_id):
667
return self._real_repository.get_revision_reconcile(revision_id)
670
def check(self, revision_ids):
672
return self._real_repository.check(revision_ids)
674
def copy_content_into(self, destination, revision_id=None):
676
return self._real_repository.copy_content_into(
677
destination, revision_id=revision_id)
679
def _copy_repository_tarball(self, destination, revision_id=None):
680
# get a tarball of the remote repository, and copy from that into the
682
from bzrlib import osutils
685
from StringIO import StringIO
686
# TODO: Maybe a progress bar while streaming the tarball?
687
note("Copying repository content as tarball...")
688
tar_file = self._get_tarball('bz2')
690
tar = tarfile.open('repository', fileobj=tar_file,
692
tmpdir = tempfile.mkdtemp()
694
_extract_tar(tar, tmpdir)
695
tmp_bzrdir = BzrDir.open(tmpdir)
696
tmp_repo = tmp_bzrdir.open_repository()
697
tmp_repo.copy_content_into(destination, revision_id)
699
osutils.rmtree(tmpdir)
702
# TODO: if the server doesn't support this operation, maybe do it the
703
# slow way using the _real_repository?
705
# TODO: Suggestion from john: using external tar is much faster than
706
# python's tarfile library, but it may not work on windows.
710
"""Compress the data within the repository.
712
This is not currently implemented within the smart server.
715
return self._real_repository.pack()
717
def set_make_working_trees(self, new_value):
718
raise NotImplementedError(self.set_make_working_trees)
721
def sign_revision(self, revision_id, gpg_strategy):
723
return self._real_repository.sign_revision(revision_id, gpg_strategy)
726
def get_revisions(self, revision_ids):
728
return self._real_repository.get_revisions(revision_ids)
730
def supports_rich_root(self):
732
return self._real_repository.supports_rich_root()
734
def iter_reverse_revision_history(self, revision_id):
736
return self._real_repository.iter_reverse_revision_history(revision_id)
739
def _serializer(self):
741
return self._real_repository._serializer
743
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
745
return self._real_repository.store_revision_signature(
746
gpg_strategy, plaintext, revision_id)
748
def has_signature_for_revision_id(self, revision_id):
750
return self._real_repository.has_signature_for_revision_id(revision_id)
753
class RemoteBranchLockableFiles(LockableFiles):
754
"""A 'LockableFiles' implementation that talks to a smart server.
756
This is not a public interface class.
759
def __init__(self, bzrdir, _client):
761
self._client = _client
762
self._need_find_modes = True
763
LockableFiles.__init__(
764
self, bzrdir.get_branch_transport(None),
765
'lock', lockdir.LockDir)
767
def _find_modes(self):
768
# RemoteBranches don't let the client set the mode of control files.
769
self._dir_mode = None
770
self._file_mode = None
773
"""'get' a remote path as per the LockableFiles interface.
775
:param path: the file to 'get'. If this is 'branch.conf', we do not
776
just retrieve a file, instead we ask the smart server to generate
777
a configuration for us - which is retrieved as an INI file.
779
if path == 'branch.conf':
780
path = self.bzrdir._path_for_remote_call(self._client)
781
response = self._client.call_expecting_body(
782
'Branch.get_config_file', path)
783
assert response[0][0] == 'ok', \
784
'unexpected response code %s' % (response[0],)
785
return StringIO(response[1].read_body_bytes())
788
return LockableFiles.get(self, path)
791
class RemoteBranchFormat(branch.BranchFormat):
793
def __eq__(self, other):
794
return (isinstance(other, RemoteBranchFormat) and
795
self.__dict__ == other.__dict__)
797
def get_format_description(self):
798
return 'Remote BZR Branch'
800
def get_format_string(self):
801
return 'Remote BZR Branch'
803
def open(self, a_bzrdir):
804
assert isinstance(a_bzrdir, RemoteBzrDir)
805
return a_bzrdir.open_branch()
807
def initialize(self, a_bzrdir):
808
assert isinstance(a_bzrdir, RemoteBzrDir)
809
return a_bzrdir.create_branch()
812
class RemoteBranch(branch.Branch):
813
"""Branch stored on a server accessed by HPSS RPC.
815
At the moment most operations are mapped down to simple file operations.
818
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
820
"""Create a RemoteBranch instance.
822
:param real_branch: An optional local implementation of the branch
823
format, usually accessing the data via the VFS.
824
:param _client: Private parameter for testing.
826
# We intentionally don't call the parent class's __init__, because it
827
# will try to assign to self.tags, which is a property in this subclass.
828
# And the parent's __init__ doesn't do much anyway.
829
self._revision_history_cache = None
830
self.bzrdir = remote_bzrdir
831
if _client is not None:
832
self._client = _client
834
self._client = client._SmartClient(self.bzrdir._shared_medium)
835
self.repository = remote_repository
836
if real_branch is not None:
837
self._real_branch = real_branch
838
# Give the remote repository the matching real repo.
839
real_repo = self._real_branch.repository
840
if isinstance(real_repo, RemoteRepository):
841
real_repo._ensure_real()
842
real_repo = real_repo._real_repository
843
self.repository._set_real_repository(real_repo)
844
# Give the branch the remote repository to let fast-pathing happen.
845
self._real_branch.repository = self.repository
847
self._real_branch = None
848
# Fill out expected attributes of branch for bzrlib api users.
849
self._format = RemoteBranchFormat()
850
self.base = self.bzrdir.root_transport.base
851
self._control_files = None
852
self._lock_mode = None
853
self._lock_token = None
855
self._leave_lock = False
858
return "%s(%s)" % (self.__class__.__name__, self.base)
862
def _ensure_real(self):
863
"""Ensure that there is a _real_branch set.
865
Used before calls to self._real_branch.
867
if not self._real_branch:
868
assert vfs.vfs_enabled()
869
self.bzrdir._ensure_real()
870
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
871
# Give the remote repository the matching real repo.
872
real_repo = self._real_branch.repository
873
if isinstance(real_repo, RemoteRepository):
874
real_repo._ensure_real()
875
real_repo = real_repo._real_repository
876
self.repository._set_real_repository(real_repo)
877
# Give the branch the remote repository to let fast-pathing happen.
878
self._real_branch.repository = self.repository
879
# XXX: deal with _lock_mode == 'w'
880
if self._lock_mode == 'r':
881
self._real_branch.lock_read()
884
def control_files(self):
885
# Defer actually creating RemoteBranchLockableFiles until its needed,
886
# because it triggers an _ensure_real that we otherwise might not need.
887
if self._control_files is None:
888
self._control_files = RemoteBranchLockableFiles(
889
self.bzrdir, self._client)
890
return self._control_files
892
def _get_checkout_format(self):
894
return self._real_branch._get_checkout_format()
896
def get_physical_lock_status(self):
897
"""See Branch.get_physical_lock_status()."""
898
# should be an API call to the server, as branches must be lockable.
900
return self._real_branch.get_physical_lock_status()
903
if not self._lock_mode:
904
self._lock_mode = 'r'
906
if self._real_branch is not None:
907
self._real_branch.lock_read()
909
self._lock_count += 1
911
def _remote_lock_write(self, token):
913
branch_token = repo_token = ''
916
repo_token = self.repository.lock_write()
917
self.repository.unlock()
918
path = self.bzrdir._path_for_remote_call(self._client)
919
response = self._client.call('Branch.lock_write', path, branch_token,
921
if response[0] == 'ok':
922
ok, branch_token, repo_token = response
923
return branch_token, repo_token
924
elif response[0] == 'LockContention':
925
raise errors.LockContention('(remote lock)')
926
elif response[0] == 'TokenMismatch':
927
raise errors.TokenMismatch(token, '(remote token)')
928
elif response[0] == 'UnlockableTransport':
929
raise errors.UnlockableTransport(self.bzrdir.root_transport)
930
elif response[0] == 'ReadOnlyError':
931
raise errors.ReadOnlyError(self)
933
raise errors.UnexpectedSmartServerResponse(response)
935
def lock_write(self, token=None):
936
if not self._lock_mode:
937
remote_tokens = self._remote_lock_write(token)
938
self._lock_token, self._repo_lock_token = remote_tokens
939
assert self._lock_token, 'Remote server did not return a token!'
940
# TODO: We really, really, really don't want to call _ensure_real
941
# here, but it's the easiest way to ensure coherency between the
942
# state of the RemoteBranch and RemoteRepository objects and the
943
# physical locks. If we don't materialise the real objects here,
944
# then getting everything in the right state later is complex, so
945
# for now we just do it the lazy way.
946
# -- Andrew Bennetts, 2007-02-22.
948
if self._real_branch is not None:
949
self._real_branch.repository.lock_write(
950
token=self._repo_lock_token)
952
self._real_branch.lock_write(token=self._lock_token)
954
self._real_branch.repository.unlock()
955
if token is not None:
956
self._leave_lock = True
958
# XXX: this case seems to be unreachable; token cannot be None.
959
self._leave_lock = False
960
self._lock_mode = 'w'
962
elif self._lock_mode == 'r':
963
raise errors.ReadOnlyTransaction
965
if token is not None:
966
# A token was given to lock_write, and we're relocking, so check
967
# that the given token actually matches the one we already have.
968
if token != self._lock_token:
969
raise errors.TokenMismatch(token, self._lock_token)
970
self._lock_count += 1
971
return self._lock_token
973
def _unlock(self, branch_token, repo_token):
974
path = self.bzrdir._path_for_remote_call(self._client)
975
response = self._client.call('Branch.unlock', path, branch_token,
977
if response == ('ok',):
979
elif response[0] == 'TokenMismatch':
980
raise errors.TokenMismatch(
981
str((branch_token, repo_token)), '(remote tokens)')
983
raise errors.UnexpectedSmartServerResponse(response)
986
self._lock_count -= 1
987
if not self._lock_count:
988
self._clear_cached_state()
989
mode = self._lock_mode
990
self._lock_mode = None
991
if self._real_branch is not None:
992
if not self._leave_lock:
993
# If this RemoteBranch will remove the physical lock for the
994
# repository, make sure the _real_branch doesn't do it
995
# first. (Because the _real_branch's repository is set to
996
# be the RemoteRepository.)
997
self._real_branch.repository.leave_lock_in_place()
998
self._real_branch.unlock()
1000
# Only write-locked branched need to make a remote method call
1001
# to perfom the unlock.
1003
assert self._lock_token, 'Locked, but no token!'
1004
branch_token = self._lock_token
1005
repo_token = self._repo_lock_token
1006
self._lock_token = None
1007
self._repo_lock_token = None
1008
if not self._leave_lock:
1009
self._unlock(branch_token, repo_token)
1011
def break_lock(self):
1013
return self._real_branch.break_lock()
1015
def leave_lock_in_place(self):
1016
self._leave_lock = True
1018
def dont_leave_lock_in_place(self):
1019
self._leave_lock = False
1021
def last_revision_info(self):
1022
"""See Branch.last_revision_info()."""
1023
path = self.bzrdir._path_for_remote_call(self._client)
1024
response = self._client.call('Branch.last_revision_info', path)
1025
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1026
revno = int(response[1])
1027
last_revision = response[2]
1028
return (revno, last_revision)
1030
def _gen_revision_history(self):
1031
"""See Branch._gen_revision_history()."""
1032
path = self.bzrdir._path_for_remote_call(self._client)
1033
response = self._client.call_expecting_body(
1034
'Branch.revision_history', path)
1035
assert response[0][0] == 'ok', ('unexpected response code %s'
1037
result = response[1].read_body_bytes().split('\x00')
1043
def set_revision_history(self, rev_history):
1044
# Send just the tip revision of the history; the server will generate
1045
# the full history from that. If the revision doesn't exist in this
1046
# branch, NoSuchRevision will be raised.
1047
path = self.bzrdir._path_for_remote_call(self._client)
1048
if rev_history == []:
1051
rev_id = rev_history[-1]
1052
self._clear_cached_state()
1053
response = self._client.call('Branch.set_last_revision',
1054
path, self._lock_token, self._repo_lock_token, rev_id)
1055
if response[0] == 'NoSuchRevision':
1056
raise NoSuchRevision(self, rev_id)
1058
assert response == ('ok',), (
1059
'unexpected response code %r' % (response,))
1060
self._cache_revision_history(rev_history)
1062
def get_parent(self):
1064
return self._real_branch.get_parent()
1066
def set_parent(self, url):
1068
return self._real_branch.set_parent(url)
1070
def get_config(self):
1071
return RemoteBranchConfig(self)
1073
def sprout(self, to_bzrdir, revision_id=None):
1074
# Like Branch.sprout, except that it sprouts a branch in the default
1075
# format, because RemoteBranches can't be created at arbitrary URLs.
1076
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1077
# to_bzrdir.create_branch...
1078
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1079
self.copy_content_into(result, revision_id=revision_id)
1080
result.set_parent(self.bzrdir.root_transport.base)
1084
def append_revision(self, *revision_ids):
1086
return self._real_branch.append_revision(*revision_ids)
1089
def pull(self, source, overwrite=False, stop_revision=None,
1091
# FIXME: This asks the real branch to run the hooks, which means
1092
# they're called with the wrong target branch parameter.
1093
# The test suite specifically allows this at present but it should be
1094
# fixed. It should get a _override_hook_target branch,
1095
# as push does. -- mbp 20070405
1097
self._real_branch.pull(
1098
source, overwrite=overwrite, stop_revision=stop_revision,
1102
def push(self, target, overwrite=False, stop_revision=None):
1104
return self._real_branch.push(
1105
target, overwrite=overwrite, stop_revision=stop_revision,
1106
_override_hook_source_branch=self)
1108
def is_locked(self):
1109
return self._lock_count >= 1
1111
def set_last_revision_info(self, revno, revision_id):
1113
self._clear_cached_state()
1114
return self._real_branch.set_last_revision_info(revno, revision_id)
1116
def generate_revision_history(self, revision_id, last_rev=None,
1119
return self._real_branch.generate_revision_history(
1120
revision_id, last_rev=last_rev, other_branch=other_branch)
1125
return self._real_branch.tags
1127
def set_push_location(self, location):
1129
return self._real_branch.set_push_location(location)
1131
def update_revisions(self, other, stop_revision=None):
1133
return self._real_branch.update_revisions(
1134
other, stop_revision=stop_revision)
1137
class RemoteBranchConfig(BranchConfig):
1140
self.branch._ensure_real()
1141
return self.branch._real_branch.get_config().username()
1143
def _get_branch_data_config(self):
1144
self.branch._ensure_real()
1145
if self._branch_data_config is None:
1146
self._branch_data_config = TreeConfig(self.branch._real_branch)
1147
return self._branch_data_config
1150
def _extract_tar(tar, to_dir):
1151
"""Extract all the contents of a tarfile object.
1153
A replacement for extractall, which is not present in python2.4
1156
tar.extract(tarinfo, to_dir)