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)
611
def get_signature_text(self, revision_id):
613
return self._real_repository.get_signature_text(revision_id)
616
def get_revision_graph_with_ghosts(self, revision_ids=None):
618
return self._real_repository.get_revision_graph_with_ghosts(
619
revision_ids=revision_ids)
622
def get_inventory_xml(self, revision_id):
624
return self._real_repository.get_inventory_xml(revision_id)
626
def deserialise_inventory(self, revision_id, xml):
628
return self._real_repository.deserialise_inventory(revision_id, xml)
630
def reconcile(self, other=None, thorough=False):
632
return self._real_repository.reconcile(other=other, thorough=thorough)
634
def all_revision_ids(self):
636
return self._real_repository.all_revision_ids()
639
def get_deltas_for_revisions(self, revisions):
641
return self._real_repository.get_deltas_for_revisions(revisions)
644
def get_revision_delta(self, revision_id):
646
return self._real_repository.get_revision_delta(revision_id)
649
def revision_trees(self, revision_ids):
651
return self._real_repository.revision_trees(revision_ids)
654
def get_revision_reconcile(self, revision_id):
656
return self._real_repository.get_revision_reconcile(revision_id)
659
def check(self, revision_ids):
661
return self._real_repository.check(revision_ids)
663
def copy_content_into(self, destination, revision_id=None):
665
return self._real_repository.copy_content_into(
666
destination, revision_id=revision_id)
668
def _copy_repository_tarball(self, destination, revision_id=None):
669
# get a tarball of the remote repository, and copy from that into the
671
from bzrlib import osutils
674
from StringIO import StringIO
675
# TODO: Maybe a progress bar while streaming the tarball?
676
note("Copying repository content as tarball...")
677
tar_file = self._get_tarball('bz2')
679
tar = tarfile.open('repository', fileobj=tar_file,
681
tmpdir = tempfile.mkdtemp()
683
_extract_tar(tar, tmpdir)
684
tmp_bzrdir = BzrDir.open(tmpdir)
685
tmp_repo = tmp_bzrdir.open_repository()
686
tmp_repo.copy_content_into(destination, revision_id)
688
osutils.rmtree(tmpdir)
691
# TODO: if the server doesn't support this operation, maybe do it the
692
# slow way using the _real_repository?
694
# TODO: Suggestion from john: using external tar is much faster than
695
# python's tarfile library, but it may not work on windows.
699
"""Compress the data within the repository.
701
This is not currently implemented within the smart server.
704
return self._real_repository.pack()
706
def set_make_working_trees(self, new_value):
707
raise NotImplementedError(self.set_make_working_trees)
710
def sign_revision(self, revision_id, gpg_strategy):
712
return self._real_repository.sign_revision(revision_id, gpg_strategy)
715
def get_revisions(self, revision_ids):
717
return self._real_repository.get_revisions(revision_ids)
719
def supports_rich_root(self):
721
return self._real_repository.supports_rich_root()
723
def iter_reverse_revision_history(self, revision_id):
725
return self._real_repository.iter_reverse_revision_history(revision_id)
728
def _serializer(self):
730
return self._real_repository._serializer
732
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
734
return self._real_repository.store_revision_signature(
735
gpg_strategy, plaintext, revision_id)
737
def has_signature_for_revision_id(self, revision_id):
739
return self._real_repository.has_signature_for_revision_id(revision_id)
742
class RemoteBranchLockableFiles(LockableFiles):
743
"""A 'LockableFiles' implementation that talks to a smart server.
745
This is not a public interface class.
748
def __init__(self, bzrdir, _client):
750
self._client = _client
751
self._need_find_modes = True
752
LockableFiles.__init__(
753
self, bzrdir.get_branch_transport(None),
754
'lock', lockdir.LockDir)
756
def _find_modes(self):
757
# RemoteBranches don't let the client set the mode of control files.
758
self._dir_mode = None
759
self._file_mode = None
762
"""'get' a remote path as per the LockableFiles interface.
764
:param path: the file to 'get'. If this is 'branch.conf', we do not
765
just retrieve a file, instead we ask the smart server to generate
766
a configuration for us - which is retrieved as an INI file.
768
if path == 'branch.conf':
769
path = self.bzrdir._path_for_remote_call(self._client)
770
response = self._client.call_expecting_body(
771
'Branch.get_config_file', path)
772
assert response[0][0] == 'ok', \
773
'unexpected response code %s' % (response[0],)
774
return StringIO(response[1].read_body_bytes())
777
return LockableFiles.get(self, path)
780
class RemoteBranchFormat(branch.BranchFormat):
782
def __eq__(self, other):
783
return (isinstance(other, RemoteBranchFormat) and
784
self.__dict__ == other.__dict__)
786
def get_format_description(self):
787
return 'Remote BZR Branch'
789
def get_format_string(self):
790
return 'Remote BZR Branch'
792
def open(self, a_bzrdir):
793
assert isinstance(a_bzrdir, RemoteBzrDir)
794
return a_bzrdir.open_branch()
796
def initialize(self, a_bzrdir):
797
assert isinstance(a_bzrdir, RemoteBzrDir)
798
return a_bzrdir.create_branch()
801
class RemoteBranch(branch.Branch):
802
"""Branch stored on a server accessed by HPSS RPC.
804
At the moment most operations are mapped down to simple file operations.
807
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
809
"""Create a RemoteBranch instance.
811
:param real_branch: An optional local implementation of the branch
812
format, usually accessing the data via the VFS.
813
:param _client: Private parameter for testing.
815
# We intentionally don't call the parent class's __init__, because it
816
# will try to assign to self.tags, which is a property in this subclass.
817
# And the parent's __init__ doesn't do much anyway.
818
self._revision_history_cache = None
819
self.bzrdir = remote_bzrdir
820
if _client is not None:
821
self._client = _client
823
self._client = client._SmartClient(self.bzrdir._shared_medium)
824
self.repository = remote_repository
825
if real_branch is not None:
826
self._real_branch = real_branch
827
# Give the remote repository the matching real repo.
828
real_repo = self._real_branch.repository
829
if isinstance(real_repo, RemoteRepository):
830
real_repo._ensure_real()
831
real_repo = real_repo._real_repository
832
self.repository._set_real_repository(real_repo)
833
# Give the branch the remote repository to let fast-pathing happen.
834
self._real_branch.repository = self.repository
836
self._real_branch = None
837
# Fill out expected attributes of branch for bzrlib api users.
838
self._format = RemoteBranchFormat()
839
self.base = self.bzrdir.root_transport.base
840
self._control_files = None
841
self._lock_mode = None
842
self._lock_token = None
844
self._leave_lock = False
847
return "%s(%s)" % (self.__class__.__name__, self.base)
851
def _ensure_real(self):
852
"""Ensure that there is a _real_branch set.
854
Used before calls to self._real_branch.
856
if not self._real_branch:
857
assert vfs.vfs_enabled()
858
self.bzrdir._ensure_real()
859
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
860
# Give the remote repository the matching real repo.
861
real_repo = self._real_branch.repository
862
if isinstance(real_repo, RemoteRepository):
863
real_repo._ensure_real()
864
real_repo = real_repo._real_repository
865
self.repository._set_real_repository(real_repo)
866
# Give the branch the remote repository to let fast-pathing happen.
867
self._real_branch.repository = self.repository
868
# XXX: deal with _lock_mode == 'w'
869
if self._lock_mode == 'r':
870
self._real_branch.lock_read()
873
def control_files(self):
874
# Defer actually creating RemoteBranchLockableFiles until its needed,
875
# because it triggers an _ensure_real that we otherwise might not need.
876
if self._control_files is None:
877
self._control_files = RemoteBranchLockableFiles(
878
self.bzrdir, self._client)
879
return self._control_files
881
def _get_checkout_format(self):
883
return self._real_branch._get_checkout_format()
885
def get_physical_lock_status(self):
886
"""See Branch.get_physical_lock_status()."""
887
# should be an API call to the server, as branches must be lockable.
889
return self._real_branch.get_physical_lock_status()
892
if not self._lock_mode:
893
self._lock_mode = 'r'
895
if self._real_branch is not None:
896
self._real_branch.lock_read()
898
self._lock_count += 1
900
def _remote_lock_write(self, token):
902
branch_token = repo_token = ''
905
repo_token = self.repository.lock_write()
906
self.repository.unlock()
907
path = self.bzrdir._path_for_remote_call(self._client)
908
response = self._client.call('Branch.lock_write', path, branch_token,
910
if response[0] == 'ok':
911
ok, branch_token, repo_token = response
912
return branch_token, repo_token
913
elif response[0] == 'LockContention':
914
raise errors.LockContention('(remote lock)')
915
elif response[0] == 'TokenMismatch':
916
raise errors.TokenMismatch(token, '(remote token)')
917
elif response[0] == 'UnlockableTransport':
918
raise errors.UnlockableTransport(self.bzrdir.root_transport)
919
elif response[0] == 'ReadOnlyError':
920
raise errors.ReadOnlyError(self)
922
raise errors.UnexpectedSmartServerResponse(response)
924
def lock_write(self, token=None):
925
if not self._lock_mode:
926
remote_tokens = self._remote_lock_write(token)
927
self._lock_token, self._repo_lock_token = remote_tokens
928
assert self._lock_token, 'Remote server did not return a token!'
929
# TODO: We really, really, really don't want to call _ensure_real
930
# here, but it's the easiest way to ensure coherency between the
931
# state of the RemoteBranch and RemoteRepository objects and the
932
# physical locks. If we don't materialise the real objects here,
933
# then getting everything in the right state later is complex, so
934
# for now we just do it the lazy way.
935
# -- Andrew Bennetts, 2007-02-22.
937
if self._real_branch is not None:
938
self._real_branch.repository.lock_write(
939
token=self._repo_lock_token)
941
self._real_branch.lock_write(token=self._lock_token)
943
self._real_branch.repository.unlock()
944
if token is not None:
945
self._leave_lock = True
947
# XXX: this case seems to be unreachable; token cannot be None.
948
self._leave_lock = False
949
self._lock_mode = 'w'
951
elif self._lock_mode == 'r':
952
raise errors.ReadOnlyTransaction
954
if token is not None:
955
# A token was given to lock_write, and we're relocking, so check
956
# that the given token actually matches the one we already have.
957
if token != self._lock_token:
958
raise errors.TokenMismatch(token, self._lock_token)
959
self._lock_count += 1
960
return self._lock_token
962
def _unlock(self, branch_token, repo_token):
963
path = self.bzrdir._path_for_remote_call(self._client)
964
response = self._client.call('Branch.unlock', path, branch_token,
966
if response == ('ok',):
968
elif response[0] == 'TokenMismatch':
969
raise errors.TokenMismatch(
970
str((branch_token, repo_token)), '(remote tokens)')
972
raise errors.UnexpectedSmartServerResponse(response)
975
self._lock_count -= 1
976
if not self._lock_count:
977
self._clear_cached_state()
978
mode = self._lock_mode
979
self._lock_mode = None
980
if self._real_branch is not None:
981
if not self._leave_lock:
982
# If this RemoteBranch will remove the physical lock for the
983
# repository, make sure the _real_branch doesn't do it
984
# first. (Because the _real_branch's repository is set to
985
# be the RemoteRepository.)
986
self._real_branch.repository.leave_lock_in_place()
987
self._real_branch.unlock()
989
# Only write-locked branched need to make a remote method call
990
# to perfom the unlock.
992
assert self._lock_token, 'Locked, but no token!'
993
branch_token = self._lock_token
994
repo_token = self._repo_lock_token
995
self._lock_token = None
996
self._repo_lock_token = None
997
if not self._leave_lock:
998
self._unlock(branch_token, repo_token)
1000
def break_lock(self):
1002
return self._real_branch.break_lock()
1004
def leave_lock_in_place(self):
1005
self._leave_lock = True
1007
def dont_leave_lock_in_place(self):
1008
self._leave_lock = False
1010
def last_revision_info(self):
1011
"""See Branch.last_revision_info()."""
1012
path = self.bzrdir._path_for_remote_call(self._client)
1013
response = self._client.call('Branch.last_revision_info', path)
1014
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1015
revno = int(response[1])
1016
last_revision = response[2]
1017
return (revno, last_revision)
1019
def _gen_revision_history(self):
1020
"""See Branch._gen_revision_history()."""
1021
path = self.bzrdir._path_for_remote_call(self._client)
1022
response = self._client.call_expecting_body(
1023
'Branch.revision_history', path)
1024
assert response[0][0] == 'ok', ('unexpected response code %s'
1026
result = response[1].read_body_bytes().split('\x00')
1032
def set_revision_history(self, rev_history):
1033
# Send just the tip revision of the history; the server will generate
1034
# the full history from that. If the revision doesn't exist in this
1035
# branch, NoSuchRevision will be raised.
1036
path = self.bzrdir._path_for_remote_call(self._client)
1037
if rev_history == []:
1040
rev_id = rev_history[-1]
1041
self._clear_cached_state()
1042
response = self._client.call('Branch.set_last_revision',
1043
path, self._lock_token, self._repo_lock_token, rev_id)
1044
if response[0] == 'NoSuchRevision':
1045
raise NoSuchRevision(self, rev_id)
1047
assert response == ('ok',), (
1048
'unexpected response code %r' % (response,))
1049
self._cache_revision_history(rev_history)
1051
def get_parent(self):
1053
return self._real_branch.get_parent()
1055
def set_parent(self, url):
1057
return self._real_branch.set_parent(url)
1059
def get_config(self):
1060
return RemoteBranchConfig(self)
1062
def sprout(self, to_bzrdir, revision_id=None):
1063
# Like Branch.sprout, except that it sprouts a branch in the default
1064
# format, because RemoteBranches can't be created at arbitrary URLs.
1065
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1066
# to_bzrdir.create_branch...
1067
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1068
self.copy_content_into(result, revision_id=revision_id)
1069
result.set_parent(self.bzrdir.root_transport.base)
1073
def append_revision(self, *revision_ids):
1075
return self._real_branch.append_revision(*revision_ids)
1078
def pull(self, source, overwrite=False, stop_revision=None,
1080
# FIXME: This asks the real branch to run the hooks, which means
1081
# they're called with the wrong target branch parameter.
1082
# The test suite specifically allows this at present but it should be
1083
# fixed. It should get a _override_hook_target branch,
1084
# as push does. -- mbp 20070405
1086
self._real_branch.pull(
1087
source, overwrite=overwrite, stop_revision=stop_revision,
1091
def push(self, target, overwrite=False, stop_revision=None):
1093
return self._real_branch.push(
1094
target, overwrite=overwrite, stop_revision=stop_revision,
1095
_override_hook_source_branch=self)
1097
def is_locked(self):
1098
return self._lock_count >= 1
1100
def set_last_revision_info(self, revno, revision_id):
1102
self._clear_cached_state()
1103
return self._real_branch.set_last_revision_info(revno, revision_id)
1105
def generate_revision_history(self, revision_id, last_rev=None,
1108
return self._real_branch.generate_revision_history(
1109
revision_id, last_rev=last_rev, other_branch=other_branch)
1114
return self._real_branch.tags
1116
def set_push_location(self, location):
1118
return self._real_branch.set_push_location(location)
1120
def update_revisions(self, other, stop_revision=None):
1122
return self._real_branch.update_revisions(
1123
other, stop_revision=stop_revision)
1126
class RemoteBranchConfig(BranchConfig):
1129
self.branch._ensure_real()
1130
return self.branch._real_branch.get_config().username()
1132
def _get_branch_data_config(self):
1133
self.branch._ensure_real()
1134
if self._branch_data_config is None:
1135
self._branch_data_config = TreeConfig(self.branch._real_branch)
1136
return self._branch_data_config
1139
def _extract_tar(tar, to_dir):
1140
"""Extract all the contents of a tarfile object.
1142
A replacement for extractall, which is not present in python2.4
1145
tar.extract(tarinfo, to_dir)