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._medium = transport.get_smart_client()
56
self._client = client._SmartClient(self._medium)
58
self._client = _client
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._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 compatability 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 compatability 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 = list(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 get_graph(self, other_repository=None):
325
"""Return the graph for this repository format"""
326
return self._real_repository.get_graph(other_repository)
328
def gather_stats(self, revid=None, committers=None):
329
"""See Repository.gather_stats()."""
330
path = self.bzrdir._path_for_remote_call(self._client)
331
if revid in (None, NULL_REVISION):
335
if committers is None or not committers:
336
fmt_committers = 'no'
338
fmt_committers = 'yes'
339
response = self._client.call_expecting_body(
340
'Repository.gather_stats', path, fmt_revid, fmt_committers)
341
assert response[0][0] == 'ok', \
342
'unexpected response code %s' % (response[0],)
344
body = response[1].read_body_bytes()
346
for line in body.split('\n'):
349
key, val_text = line.split(':')
350
if key in ('revisions', 'size', 'committers'):
351
result[key] = int(val_text)
352
elif key in ('firstrev', 'latestrev'):
353
values = val_text.split(' ')[1:]
354
result[key] = (float(values[0]), long(values[1]))
358
def get_physical_lock_status(self):
359
"""See Repository.get_physical_lock_status()."""
362
def is_in_write_group(self):
363
"""Return True if there is an open write group.
365
write groups are only applicable locally for the smart server..
367
if self._real_repository:
368
return self._real_repository.is_in_write_group()
371
return self._lock_count >= 1
374
"""See Repository.is_shared()."""
375
path = self.bzrdir._path_for_remote_call(self._client)
376
response = self._client.call('Repository.is_shared', path)
377
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
378
return response[0] == 'yes'
381
# wrong eventually - want a local lock cache context
382
if not self._lock_mode:
383
self._lock_mode = 'r'
385
if self._real_repository is not None:
386
self._real_repository.lock_read()
388
self._lock_count += 1
390
def _remote_lock_write(self, token):
391
path = self.bzrdir._path_for_remote_call(self._client)
394
response = self._client.call('Repository.lock_write', path, token)
395
if response[0] == 'ok':
398
elif response[0] == 'LockContention':
399
raise errors.LockContention('(remote lock)')
400
elif response[0] == 'UnlockableTransport':
401
raise errors.UnlockableTransport(self.bzrdir.root_transport)
403
raise errors.UnexpectedSmartServerResponse(response)
405
def lock_write(self, token=None):
406
if not self._lock_mode:
407
self._lock_token = self._remote_lock_write(token)
408
assert self._lock_token, 'Remote server did not return a token!'
409
if self._real_repository is not None:
410
self._real_repository.lock_write(token=self._lock_token)
411
if token is not None:
412
self._leave_lock = True
414
self._leave_lock = False
415
self._lock_mode = 'w'
417
elif self._lock_mode == 'r':
418
raise errors.ReadOnlyError(self)
420
self._lock_count += 1
421
return self._lock_token
423
def leave_lock_in_place(self):
424
self._leave_lock = True
426
def dont_leave_lock_in_place(self):
427
self._leave_lock = False
429
def _set_real_repository(self, repository):
430
"""Set the _real_repository for this repository.
432
:param repository: The repository to fallback to for non-hpss
433
implemented operations.
435
assert not isinstance(repository, RemoteRepository)
436
self._real_repository = repository
437
if self._lock_mode == 'w':
438
# if we are already locked, the real repository must be able to
439
# acquire the lock with our token.
440
self._real_repository.lock_write(self._lock_token)
441
elif self._lock_mode == 'r':
442
self._real_repository.lock_read()
444
def start_write_group(self):
445
"""Start a write group on the decorated repository.
447
Smart methods peform operations in a single step so this api
448
is not really applicable except as a compatability thunk
449
for older plugins that don't use e.g. the CommitBuilder
453
return self._real_repository.start_write_group()
455
def _unlock(self, token):
456
path = self.bzrdir._path_for_remote_call(self._client)
457
response = self._client.call('Repository.unlock', path, token)
458
if response == ('ok',):
460
elif response[0] == 'TokenMismatch':
461
raise errors.TokenMismatch(token, '(remote token)')
463
raise errors.UnexpectedSmartServerResponse(response)
466
if self._lock_count == 1 and self._lock_mode == 'w':
467
# don't unlock if inside a write group.
468
if self.is_in_write_group():
469
raise errors.BzrError(
470
'Must end write groups before releasing write locks.')
471
self._lock_count -= 1
472
if not self._lock_count:
473
mode = self._lock_mode
474
self._lock_mode = None
475
if self._real_repository is not None:
476
self._real_repository.unlock()
478
# Only write-locked repositories need to make a remote method
479
# call to perfom the unlock.
481
assert self._lock_token, 'Locked, but no token!'
482
token = self._lock_token
483
self._lock_token = None
484
if not self._leave_lock:
487
def break_lock(self):
488
# should hand off to the network
490
return self._real_repository.break_lock()
492
def _get_tarball(self, compression):
493
"""Return a TemporaryFile containing a repository tarball"""
495
path = self.bzrdir._path_for_remote_call(self._client)
496
response, protocol = self._client.call_expecting_body(
497
'Repository.tarball', path, compression)
498
assert response[0] in ('ok', 'failure'), \
499
'unexpected response code %s' % (response,)
500
if response[0] == 'ok':
501
# Extract the tarball and return it
502
t = tempfile.NamedTemporaryFile()
503
# TODO: rpc layer should read directly into it...
504
t.write(protocol.read_body_bytes())
508
raise errors.SmartServerError(error_code=response)
510
def sprout(self, to_bzrdir, revision_id=None):
511
# TODO: Option to control what format is created?
512
to_repo = to_bzrdir.create_repository()
513
self._copy_repository_tarball(to_repo, revision_id)
516
### These methods are just thin shims to the VFS object for now.
518
def revision_tree(self, revision_id):
520
return self._real_repository.revision_tree(revision_id)
522
def get_serializer_format(self):
524
return self._real_repository.get_serializer_format()
526
def get_commit_builder(self, branch, parents, config, timestamp=None,
527
timezone=None, committer=None, revprops=None,
529
# FIXME: It ought to be possible to call this without immediately
530
# triggering _ensure_real. For now it's the easiest thing to do.
532
builder = self._real_repository.get_commit_builder(branch, parents,
533
config, timestamp=timestamp, timezone=timezone,
534
committer=committer, revprops=revprops, revision_id=revision_id)
535
# Make the builder use this RemoteRepository rather than the real one.
536
builder.repository = self
540
def add_inventory(self, revid, inv, parents):
542
return self._real_repository.add_inventory(revid, inv, parents)
545
def add_revision(self, rev_id, rev, inv=None, config=None):
547
return self._real_repository.add_revision(
548
rev_id, rev, inv=inv, config=config)
551
def get_inventory(self, revision_id):
553
return self._real_repository.get_inventory(revision_id)
556
def get_revision(self, revision_id):
558
return self._real_repository.get_revision(revision_id)
561
def weave_store(self):
563
return self._real_repository.weave_store
565
def get_transaction(self):
567
return self._real_repository.get_transaction()
570
def clone(self, a_bzrdir, revision_id=None):
572
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
574
def make_working_trees(self):
575
"""RemoteRepositories never create working trees by default."""
578
def fetch(self, source, revision_id=None, pb=None):
580
return self._real_repository.fetch(
581
source, revision_id=revision_id, pb=pb)
583
def create_bundle(self, target, base, fileobj, format=None):
585
self._real_repository.create_bundle(target, base, fileobj, format)
588
def control_weaves(self):
590
return self._real_repository.control_weaves
593
def get_ancestry(self, revision_id, topo_sorted=True):
595
return self._real_repository.get_ancestry(revision_id, topo_sorted)
598
def get_inventory_weave(self):
600
return self._real_repository.get_inventory_weave()
602
def fileids_altered_by_revision_ids(self, revision_ids):
604
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
607
def get_signature_text(self, revision_id):
609
return self._real_repository.get_signature_text(revision_id)
612
def get_revision_graph_with_ghosts(self, revision_ids=None):
614
return self._real_repository.get_revision_graph_with_ghosts(
615
revision_ids=revision_ids)
618
def get_inventory_xml(self, revision_id):
620
return self._real_repository.get_inventory_xml(revision_id)
622
def deserialise_inventory(self, revision_id, xml):
624
return self._real_repository.deserialise_inventory(revision_id, xml)
626
def reconcile(self, other=None, thorough=False):
628
return self._real_repository.reconcile(other=other, thorough=thorough)
630
def all_revision_ids(self):
632
return self._real_repository.all_revision_ids()
635
def get_deltas_for_revisions(self, revisions):
637
return self._real_repository.get_deltas_for_revisions(revisions)
640
def get_revision_delta(self, revision_id):
642
return self._real_repository.get_revision_delta(revision_id)
645
def revision_trees(self, revision_ids):
647
return self._real_repository.revision_trees(revision_ids)
650
def get_revision_reconcile(self, revision_id):
652
return self._real_repository.get_revision_reconcile(revision_id)
655
def check(self, revision_ids):
657
return self._real_repository.check(revision_ids)
659
def copy_content_into(self, destination, revision_id=None):
661
return self._real_repository.copy_content_into(
662
destination, revision_id=revision_id)
664
def _copy_repository_tarball(self, destination, revision_id=None):
665
# get a tarball of the remote repository, and copy from that into the
667
from bzrlib import osutils
670
from StringIO import StringIO
671
# TODO: Maybe a progress bar while streaming the tarball?
672
note("Copying repository content as tarball...")
673
tar_file = self._get_tarball('bz2')
675
tar = tarfile.open('repository', fileobj=tar_file,
677
tmpdir = tempfile.mkdtemp()
679
_extract_tar(tar, tmpdir)
680
tmp_bzrdir = BzrDir.open(tmpdir)
681
tmp_repo = tmp_bzrdir.open_repository()
682
tmp_repo.copy_content_into(destination, revision_id)
684
osutils.rmtree(tmpdir)
687
# TODO: if the server doesn't support this operation, maybe do it the
688
# slow way using the _real_repository?
690
# TODO: Suggestion from john: using external tar is much faster than
691
# python's tarfile library, but it may not work on windows.
695
"""Compress the data within the repository.
697
This is not currently implemented within the smart server.
700
return self._real_repository.pack()
702
def set_make_working_trees(self, new_value):
703
raise NotImplementedError(self.set_make_working_trees)
706
def sign_revision(self, revision_id, gpg_strategy):
708
return self._real_repository.sign_revision(revision_id, gpg_strategy)
711
def get_revisions(self, revision_ids):
713
return self._real_repository.get_revisions(revision_ids)
715
def supports_rich_root(self):
717
return self._real_repository.supports_rich_root()
719
def iter_reverse_revision_history(self, revision_id):
721
return self._real_repository.iter_reverse_revision_history(revision_id)
724
def _serializer(self):
726
return self._real_repository._serializer
728
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
730
return self._real_repository.store_revision_signature(
731
gpg_strategy, plaintext, revision_id)
733
def has_signature_for_revision_id(self, revision_id):
735
return self._real_repository.has_signature_for_revision_id(revision_id)
738
class RemoteBranchLockableFiles(LockableFiles):
739
"""A 'LockableFiles' implementation that talks to a smart server.
741
This is not a public interface class.
744
def __init__(self, bzrdir, _client):
746
self._client = _client
747
self._need_find_modes = True
748
LockableFiles.__init__(
749
self, bzrdir.get_branch_transport(None),
750
'lock', lockdir.LockDir)
752
def _find_modes(self):
753
# RemoteBranches don't let the client set the mode of control files.
754
self._dir_mode = None
755
self._file_mode = None
758
"""'get' a remote path as per the LockableFiles interface.
760
:param path: the file to 'get'. If this is 'branch.conf', we do not
761
just retrieve a file, instead we ask the smart server to generate
762
a configuration for us - which is retrieved as an INI file.
764
if path == 'branch.conf':
765
path = self.bzrdir._path_for_remote_call(self._client)
766
response = self._client.call_expecting_body(
767
'Branch.get_config_file', path)
768
assert response[0][0] == 'ok', \
769
'unexpected response code %s' % (response[0],)
770
return StringIO(response[1].read_body_bytes())
773
return LockableFiles.get(self, path)
776
class RemoteBranchFormat(branch.BranchFormat):
778
def __eq__(self, other):
779
return (isinstance(other, RemoteBranchFormat) and
780
self.__dict__ == other.__dict__)
782
def get_format_description(self):
783
return 'Remote BZR Branch'
785
def get_format_string(self):
786
return 'Remote BZR Branch'
788
def open(self, a_bzrdir):
789
assert isinstance(a_bzrdir, RemoteBzrDir)
790
return a_bzrdir.open_branch()
792
def initialize(self, a_bzrdir):
793
assert isinstance(a_bzrdir, RemoteBzrDir)
794
return a_bzrdir.create_branch()
797
class RemoteBranch(branch.Branch):
798
"""Branch stored on a server accessed by HPSS RPC.
800
At the moment most operations are mapped down to simple file operations.
803
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
805
"""Create a RemoteBranch instance.
807
:param real_branch: An optional local implementation of the branch
808
format, usually accessing the data via the VFS.
809
:param _client: Private parameter for testing.
811
# We intentionally don't call the parent class's __init__, because it
812
# will try to assign to self.tags, which is a property in this subclass.
813
# And the parent's __init__ doesn't do much anyway.
814
self._revision_history_cache = None
815
self.bzrdir = remote_bzrdir
816
if _client is not None:
817
self._client = _client
819
self._client = client._SmartClient(self.bzrdir._medium)
820
self.repository = remote_repository
821
if real_branch is not None:
822
self._real_branch = real_branch
823
# Give the remote repository the matching real repo.
824
real_repo = self._real_branch.repository
825
if isinstance(real_repo, RemoteRepository):
826
real_repo._ensure_real()
827
real_repo = real_repo._real_repository
828
self.repository._set_real_repository(real_repo)
829
# Give the branch the remote repository to let fast-pathing happen.
830
self._real_branch.repository = self.repository
832
self._real_branch = None
833
# Fill out expected attributes of branch for bzrlib api users.
834
self._format = RemoteBranchFormat()
835
self.base = self.bzrdir.root_transport.base
836
self._control_files = None
837
self._lock_mode = None
838
self._lock_token = None
840
self._leave_lock = False
843
return "%s(%s)" % (self.__class__.__name__, self.base)
847
def _ensure_real(self):
848
"""Ensure that there is a _real_branch set.
850
Used before calls to self._real_branch.
852
if not self._real_branch:
853
assert vfs.vfs_enabled()
854
self.bzrdir._ensure_real()
855
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
856
# Give the remote repository the matching real repo.
857
real_repo = self._real_branch.repository
858
if isinstance(real_repo, RemoteRepository):
859
real_repo._ensure_real()
860
real_repo = real_repo._real_repository
861
self.repository._set_real_repository(real_repo)
862
# Give the branch the remote repository to let fast-pathing happen.
863
self._real_branch.repository = self.repository
864
# XXX: deal with _lock_mode == 'w'
865
if self._lock_mode == 'r':
866
self._real_branch.lock_read()
869
def control_files(self):
870
# Defer actually creating RemoteBranchLockableFiles until its needed,
871
# because it triggers an _ensure_real that we otherwise might not need.
872
if self._control_files is None:
873
self._control_files = RemoteBranchLockableFiles(
874
self.bzrdir, self._client)
875
return self._control_files
877
def _get_checkout_format(self):
879
return self._real_branch._get_checkout_format()
881
def get_physical_lock_status(self):
882
"""See Branch.get_physical_lock_status()."""
883
# should be an API call to the server, as branches must be lockable.
885
return self._real_branch.get_physical_lock_status()
888
if not self._lock_mode:
889
self._lock_mode = 'r'
891
if self._real_branch is not None:
892
self._real_branch.lock_read()
894
self._lock_count += 1
896
def _remote_lock_write(self, token):
898
branch_token = repo_token = ''
901
repo_token = self.repository.lock_write()
902
self.repository.unlock()
903
path = self.bzrdir._path_for_remote_call(self._client)
904
response = self._client.call('Branch.lock_write', path, branch_token,
906
if response[0] == 'ok':
907
ok, branch_token, repo_token = response
908
return branch_token, repo_token
909
elif response[0] == 'LockContention':
910
raise errors.LockContention('(remote lock)')
911
elif response[0] == 'TokenMismatch':
912
raise errors.TokenMismatch(token, '(remote token)')
913
elif response[0] == 'UnlockableTransport':
914
raise errors.UnlockableTransport(self.bzrdir.root_transport)
915
elif response[0] == 'ReadOnlyError':
916
raise errors.ReadOnlyError(self)
918
raise errors.UnexpectedSmartServerResponse(response)
920
def lock_write(self, token=None):
921
if not self._lock_mode:
922
remote_tokens = self._remote_lock_write(token)
923
self._lock_token, self._repo_lock_token = remote_tokens
924
assert self._lock_token, 'Remote server did not return a token!'
925
# TODO: We really, really, really don't want to call _ensure_real
926
# here, but it's the easiest way to ensure coherency between the
927
# state of the RemoteBranch and RemoteRepository objects and the
928
# physical locks. If we don't materialise the real objects here,
929
# then getting everything in the right state later is complex, so
930
# for now we just do it the lazy way.
931
# -- Andrew Bennetts, 2007-02-22.
933
if self._real_branch is not None:
934
self._real_branch.repository.lock_write(
935
token=self._repo_lock_token)
937
self._real_branch.lock_write(token=self._lock_token)
939
self._real_branch.repository.unlock()
940
if token is not None:
941
self._leave_lock = True
943
# XXX: this case seems to be unreachable; token cannot be None.
944
self._leave_lock = False
945
self._lock_mode = 'w'
947
elif self._lock_mode == 'r':
948
raise errors.ReadOnlyTransaction
950
if token is not None:
951
# A token was given to lock_write, and we're relocking, so check
952
# that the given token actually matches the one we already have.
953
if token != self._lock_token:
954
raise errors.TokenMismatch(token, self._lock_token)
955
self._lock_count += 1
956
return self._lock_token
958
def _unlock(self, branch_token, repo_token):
959
path = self.bzrdir._path_for_remote_call(self._client)
960
response = self._client.call('Branch.unlock', path, branch_token,
962
if response == ('ok',):
964
elif response[0] == 'TokenMismatch':
965
raise errors.TokenMismatch(
966
str((branch_token, repo_token)), '(remote tokens)')
968
raise errors.UnexpectedSmartServerResponse(response)
971
self._lock_count -= 1
972
if not self._lock_count:
973
self._clear_cached_state()
974
mode = self._lock_mode
975
self._lock_mode = None
976
if self._real_branch is not None:
977
if not self._leave_lock:
978
# If this RemoteBranch will remove the physical lock for the
979
# repository, make sure the _real_branch doesn't do it
980
# first. (Because the _real_branch's repository is set to
981
# be the RemoteRepository.)
982
self._real_branch.repository.leave_lock_in_place()
983
self._real_branch.unlock()
985
# Only write-locked branched need to make a remote method call
986
# to perfom the unlock.
988
assert self._lock_token, 'Locked, but no token!'
989
branch_token = self._lock_token
990
repo_token = self._repo_lock_token
991
self._lock_token = None
992
self._repo_lock_token = None
993
if not self._leave_lock:
994
self._unlock(branch_token, repo_token)
996
def break_lock(self):
998
return self._real_branch.break_lock()
1000
def leave_lock_in_place(self):
1001
self._leave_lock = True
1003
def dont_leave_lock_in_place(self):
1004
self._leave_lock = False
1006
def last_revision_info(self):
1007
"""See Branch.last_revision_info()."""
1008
path = self.bzrdir._path_for_remote_call(self._client)
1009
response = self._client.call('Branch.last_revision_info', path)
1010
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1011
revno = int(response[1])
1012
last_revision = response[2]
1013
return (revno, last_revision)
1015
def _gen_revision_history(self):
1016
"""See Branch._gen_revision_history()."""
1017
path = self.bzrdir._path_for_remote_call(self._client)
1018
response = self._client.call_expecting_body(
1019
'Branch.revision_history', path)
1020
assert response[0][0] == 'ok', ('unexpected response code %s'
1022
result = response[1].read_body_bytes().split('\x00')
1028
def set_revision_history(self, rev_history):
1029
# Send just the tip revision of the history; the server will generate
1030
# the full history from that. If the revision doesn't exist in this
1031
# branch, NoSuchRevision will be raised.
1032
path = self.bzrdir._path_for_remote_call(self._client)
1033
if rev_history == []:
1036
rev_id = rev_history[-1]
1037
self._clear_cached_state()
1038
response = self._client.call('Branch.set_last_revision',
1039
path, self._lock_token, self._repo_lock_token, rev_id)
1040
if response[0] == 'NoSuchRevision':
1041
raise NoSuchRevision(self, rev_id)
1043
assert response == ('ok',), (
1044
'unexpected response code %r' % (response,))
1045
self._cache_revision_history(rev_history)
1047
def get_parent(self):
1049
return self._real_branch.get_parent()
1051
def set_parent(self, url):
1053
return self._real_branch.set_parent(url)
1055
def get_config(self):
1056
return RemoteBranchConfig(self)
1058
def sprout(self, to_bzrdir, revision_id=None):
1059
# Like Branch.sprout, except that it sprouts a branch in the default
1060
# format, because RemoteBranches can't be created at arbitrary URLs.
1061
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1062
# to_bzrdir.create_branch...
1063
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1064
self.copy_content_into(result, revision_id=revision_id)
1065
result.set_parent(self.bzrdir.root_transport.base)
1069
def append_revision(self, *revision_ids):
1071
return self._real_branch.append_revision(*revision_ids)
1074
def pull(self, source, overwrite=False, stop_revision=None,
1076
# FIXME: This asks the real branch to run the hooks, which means
1077
# they're called with the wrong target branch parameter.
1078
# The test suite specifically allows this at present but it should be
1079
# fixed. It should get a _override_hook_target branch,
1080
# as push does. -- mbp 20070405
1082
self._real_branch.pull(
1083
source, overwrite=overwrite, stop_revision=stop_revision,
1087
def push(self, target, overwrite=False, stop_revision=None):
1089
return self._real_branch.push(
1090
target, overwrite=overwrite, stop_revision=stop_revision,
1091
_override_hook_source_branch=self)
1093
def is_locked(self):
1094
return self._lock_count >= 1
1096
def set_last_revision_info(self, revno, revision_id):
1098
self._clear_cached_state()
1099
return self._real_branch.set_last_revision_info(revno, revision_id)
1101
def generate_revision_history(self, revision_id, last_rev=None,
1104
return self._real_branch.generate_revision_history(
1105
revision_id, last_rev=last_rev, other_branch=other_branch)
1110
return self._real_branch.tags
1112
def set_push_location(self, location):
1114
return self._real_branch.set_push_location(location)
1116
def update_revisions(self, other, stop_revision=None):
1118
return self._real_branch.update_revisions(
1119
other, stop_revision=stop_revision)
1122
class RemoteBranchConfig(BranchConfig):
1125
self.branch._ensure_real()
1126
return self.branch._real_branch.get_config().username()
1128
def _get_branch_data_config(self):
1129
self.branch._ensure_real()
1130
if self._branch_data_config is None:
1131
self._branch_data_config = TreeConfig(self.branch._real_branch)
1132
return self._branch_data_config
1135
def _extract_tar(tar, to_dir):
1136
"""Extract all the contents of a tarfile object.
1138
A replacement for extractall, which is not present in python2.4
1141
tar.extract(tarinfo, to_dir)