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 __eq__(self, other):
253
return (self.__class__ == other.__class__ and
254
self.bzrdir.transport.base == other.bzrdir.transport.base)
256
def __ne__(self, other):
257
return not self == other
259
def _ensure_real(self):
260
"""Ensure that there is a _real_repository set.
262
Used before calls to self._real_repository.
264
if not self._real_repository:
265
self.bzrdir._ensure_real()
266
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
267
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
269
def get_revision_graph(self, revision_id=None):
270
"""See Repository.get_revision_graph()."""
271
if revision_id is None:
273
elif revision_id == NULL_REVISION:
276
path = self.bzrdir._path_for_remote_call(self._client)
277
assert type(revision_id) is str
278
response = self._client.call_expecting_body(
279
'Repository.get_revision_graph', path, revision_id)
280
if response[0][0] not in ['ok', 'nosuchrevision']:
281
raise errors.UnexpectedSmartServerResponse(response[0])
282
if response[0][0] == 'ok':
283
coded = response[1].read_body_bytes()
285
# no revisions in this repository!
287
lines = coded.split('\n')
290
d = tuple(line.split())
291
revision_graph[d[0]] = d[1:]
293
return revision_graph
295
response_body = response[1].read_body_bytes()
296
assert response_body == ''
297
raise NoSuchRevision(self, revision_id)
299
def has_revision(self, revision_id):
300
"""See Repository.has_revision()."""
301
if revision_id is None:
302
# The null revision is always present.
304
path = self.bzrdir._path_for_remote_call(self._client)
305
response = self._client.call('Repository.has_revision', path, revision_id)
306
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
307
return response[0] == 'yes'
309
def get_graph(self, other_repository=None):
310
"""Return the graph for this repository format"""
311
return self._real_repository.get_graph(other_repository)
313
def gather_stats(self, revid=None, committers=None):
314
"""See Repository.gather_stats()."""
315
path = self.bzrdir._path_for_remote_call(self._client)
316
if revid in (None, NULL_REVISION):
320
if committers is None or not committers:
321
fmt_committers = 'no'
323
fmt_committers = 'yes'
324
response = self._client.call_expecting_body(
325
'Repository.gather_stats', path, fmt_revid, fmt_committers)
326
assert response[0][0] == 'ok', \
327
'unexpected response code %s' % (response[0],)
329
body = response[1].read_body_bytes()
331
for line in body.split('\n'):
334
key, val_text = line.split(':')
335
if key in ('revisions', 'size', 'committers'):
336
result[key] = int(val_text)
337
elif key in ('firstrev', 'latestrev'):
338
values = val_text.split(' ')[1:]
339
result[key] = (float(values[0]), long(values[1]))
343
def get_physical_lock_status(self):
344
"""See Repository.get_physical_lock_status()."""
348
"""See Repository.is_shared()."""
349
path = self.bzrdir._path_for_remote_call(self._client)
350
response = self._client.call('Repository.is_shared', path)
351
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
352
return response[0] == 'yes'
355
# wrong eventually - want a local lock cache context
356
if not self._lock_mode:
357
self._lock_mode = 'r'
359
if self._real_repository is not None:
360
self._real_repository.lock_read()
362
self._lock_count += 1
364
def _remote_lock_write(self, token):
365
path = self.bzrdir._path_for_remote_call(self._client)
368
response = self._client.call('Repository.lock_write', path, token)
369
if response[0] == 'ok':
372
elif response[0] == 'LockContention':
373
raise errors.LockContention('(remote lock)')
374
elif response[0] == 'UnlockableTransport':
375
raise errors.UnlockableTransport(self.bzrdir.root_transport)
377
raise errors.UnexpectedSmartServerResponse(response)
379
def lock_write(self, token=None):
380
if not self._lock_mode:
381
self._lock_token = self._remote_lock_write(token)
382
assert self._lock_token, 'Remote server did not return a token!'
383
if self._real_repository is not None:
384
self._real_repository.lock_write(token=self._lock_token)
385
if token is not None:
386
self._leave_lock = True
388
self._leave_lock = False
389
self._lock_mode = 'w'
391
elif self._lock_mode == 'r':
392
raise errors.ReadOnlyError(self)
394
self._lock_count += 1
395
return self._lock_token
397
def leave_lock_in_place(self):
398
self._leave_lock = True
400
def dont_leave_lock_in_place(self):
401
self._leave_lock = False
403
def _set_real_repository(self, repository):
404
"""Set the _real_repository for this repository.
406
:param repository: The repository to fallback to for non-hpss
407
implemented operations.
409
assert not isinstance(repository, RemoteRepository)
410
self._real_repository = repository
411
if self._lock_mode == 'w':
412
# if we are already locked, the real repository must be able to
413
# acquire the lock with our token.
414
self._real_repository.lock_write(self._lock_token)
415
elif self._lock_mode == 'r':
416
self._real_repository.lock_read()
418
def _unlock(self, token):
419
path = self.bzrdir._path_for_remote_call(self._client)
420
response = self._client.call('Repository.unlock', path, token)
421
if response == ('ok',):
423
elif response[0] == 'TokenMismatch':
424
raise errors.TokenMismatch(token, '(remote token)')
426
raise errors.UnexpectedSmartServerResponse(response)
429
self._lock_count -= 1
430
if not self._lock_count:
431
mode = self._lock_mode
432
self._lock_mode = None
433
if self._real_repository is not None:
434
self._real_repository.unlock()
436
# Only write-locked repositories need to make a remote method
437
# call to perfom the unlock.
439
assert self._lock_token, 'Locked, but no token!'
440
token = self._lock_token
441
self._lock_token = None
442
if not self._leave_lock:
445
def break_lock(self):
446
# should hand off to the network
448
return self._real_repository.break_lock()
450
def _get_tarball(self, compression):
451
"""Return a TemporaryFile containing a repository tarball"""
453
path = self.bzrdir._path_for_remote_call(self._client)
454
response, protocol = self._client.call_expecting_body(
455
'Repository.tarball', path, compression)
456
assert response[0] in ('ok', 'failure'), \
457
'unexpected response code %s' % (response,)
458
if response[0] == 'ok':
459
# Extract the tarball and return it
460
t = tempfile.NamedTemporaryFile()
461
# TODO: rpc layer should read directly into it...
462
t.write(protocol.read_body_bytes())
466
raise errors.SmartServerError(error_code=response)
468
def sprout(self, to_bzrdir, revision_id=None):
469
# TODO: Option to control what format is created?
470
to_repo = to_bzrdir.create_repository()
471
self._copy_repository_tarball(to_repo, revision_id)
474
### These methods are just thin shims to the VFS object for now.
476
def revision_tree(self, revision_id):
478
return self._real_repository.revision_tree(revision_id)
480
def get_serializer_format(self):
482
return self._real_repository.get_serializer_format()
484
def get_commit_builder(self, branch, parents, config, timestamp=None,
485
timezone=None, committer=None, revprops=None,
487
# FIXME: It ought to be possible to call this without immediately
488
# triggering _ensure_real. For now it's the easiest thing to do.
490
builder = self._real_repository.get_commit_builder(branch, parents,
491
config, timestamp=timestamp, timezone=timezone,
492
committer=committer, revprops=revprops, revision_id=revision_id)
493
# Make the builder use this RemoteRepository rather than the real one.
494
builder.repository = self
498
def add_inventory(self, revid, inv, parents):
500
return self._real_repository.add_inventory(revid, inv, parents)
503
def add_revision(self, rev_id, rev, inv=None, config=None):
505
return self._real_repository.add_revision(
506
rev_id, rev, inv=inv, config=config)
509
def get_inventory(self, revision_id):
511
return self._real_repository.get_inventory(revision_id)
514
def get_revision(self, revision_id):
516
return self._real_repository.get_revision(revision_id)
519
def weave_store(self):
521
return self._real_repository.weave_store
523
def get_transaction(self):
525
return self._real_repository.get_transaction()
528
def clone(self, a_bzrdir, revision_id=None):
530
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
532
def make_working_trees(self):
533
"""RemoteRepositories never create working trees by default."""
536
def fetch(self, source, revision_id=None, pb=None):
538
return self._real_repository.fetch(
539
source, revision_id=revision_id, pb=pb)
541
def create_bundle(self, target, base, fileobj, format=None):
543
self._real_repository.create_bundle(target, base, fileobj, format)
546
def control_weaves(self):
548
return self._real_repository.control_weaves
551
def get_ancestry(self, revision_id, topo_sorted=True):
553
return self._real_repository.get_ancestry(revision_id, topo_sorted)
556
def get_inventory_weave(self):
558
return self._real_repository.get_inventory_weave()
560
def fileids_altered_by_revision_ids(self, revision_ids):
562
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
565
def get_signature_text(self, revision_id):
567
return self._real_repository.get_signature_text(revision_id)
570
def get_revision_graph_with_ghosts(self, revision_ids=None):
572
return self._real_repository.get_revision_graph_with_ghosts(
573
revision_ids=revision_ids)
576
def get_inventory_xml(self, revision_id):
578
return self._real_repository.get_inventory_xml(revision_id)
580
def deserialise_inventory(self, revision_id, xml):
582
return self._real_repository.deserialise_inventory(revision_id, xml)
584
def reconcile(self, other=None, thorough=False):
586
return self._real_repository.reconcile(other=other, thorough=thorough)
588
def all_revision_ids(self):
590
return self._real_repository.all_revision_ids()
593
def get_deltas_for_revisions(self, revisions):
595
return self._real_repository.get_deltas_for_revisions(revisions)
598
def get_revision_delta(self, revision_id):
600
return self._real_repository.get_revision_delta(revision_id)
603
def revision_trees(self, revision_ids):
605
return self._real_repository.revision_trees(revision_ids)
608
def get_revision_reconcile(self, revision_id):
610
return self._real_repository.get_revision_reconcile(revision_id)
613
def check(self, revision_ids):
615
return self._real_repository.check(revision_ids)
617
def copy_content_into(self, destination, revision_id=None):
619
return self._real_repository.copy_content_into(
620
destination, revision_id=revision_id)
622
def _copy_repository_tarball(self, destination, revision_id=None):
623
# get a tarball of the remote repository, and copy from that into the
625
from bzrlib import osutils
628
from StringIO import StringIO
629
# TODO: Maybe a progress bar while streaming the tarball?
630
note("Copying repository content as tarball...")
631
tar_file = self._get_tarball('bz2')
633
tar = tarfile.open('repository', fileobj=tar_file,
635
tmpdir = tempfile.mkdtemp()
637
_extract_tar(tar, tmpdir)
638
tmp_bzrdir = BzrDir.open(tmpdir)
639
tmp_repo = tmp_bzrdir.open_repository()
640
tmp_repo.copy_content_into(destination, revision_id)
642
osutils.rmtree(tmpdir)
645
# TODO: if the server doesn't support this operation, maybe do it the
646
# slow way using the _real_repository?
648
# TODO: Suggestion from john: using external tar is much faster than
649
# python's tarfile library, but it may not work on windows.
653
"""Compress the data within the repository.
655
This is not currently implemented within the smart server.
658
return self._real_repository.pack()
660
def set_make_working_trees(self, new_value):
661
raise NotImplementedError(self.set_make_working_trees)
664
def sign_revision(self, revision_id, gpg_strategy):
666
return self._real_repository.sign_revision(revision_id, gpg_strategy)
669
def get_revisions(self, revision_ids):
671
return self._real_repository.get_revisions(revision_ids)
673
def supports_rich_root(self):
675
return self._real_repository.supports_rich_root()
677
def iter_reverse_revision_history(self, revision_id):
679
return self._real_repository.iter_reverse_revision_history(revision_id)
682
def _serializer(self):
684
return self._real_repository._serializer
686
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
688
return self._real_repository.store_revision_signature(
689
gpg_strategy, plaintext, revision_id)
691
def has_signature_for_revision_id(self, revision_id):
693
return self._real_repository.has_signature_for_revision_id(revision_id)
696
class RemoteBranchLockableFiles(LockableFiles):
697
"""A 'LockableFiles' implementation that talks to a smart server.
699
This is not a public interface class.
702
def __init__(self, bzrdir, _client):
704
self._client = _client
705
self._need_find_modes = True
706
LockableFiles.__init__(
707
self, bzrdir.get_branch_transport(None),
708
'lock', lockdir.LockDir)
710
def _find_modes(self):
711
# RemoteBranches don't let the client set the mode of control files.
712
self._dir_mode = None
713
self._file_mode = None
716
"""'get' a remote path as per the LockableFiles interface.
718
:param path: the file to 'get'. If this is 'branch.conf', we do not
719
just retrieve a file, instead we ask the smart server to generate
720
a configuration for us - which is retrieved as an INI file.
722
if path == 'branch.conf':
723
path = self.bzrdir._path_for_remote_call(self._client)
724
response = self._client.call_expecting_body(
725
'Branch.get_config_file', path)
726
assert response[0][0] == 'ok', \
727
'unexpected response code %s' % (response[0],)
728
return StringIO(response[1].read_body_bytes())
731
return LockableFiles.get(self, path)
734
class RemoteBranchFormat(branch.BranchFormat):
736
def __eq__(self, other):
737
return (isinstance(other, RemoteBranchFormat) and
738
self.__dict__ == other.__dict__)
740
def get_format_description(self):
741
return 'Remote BZR Branch'
743
def get_format_string(self):
744
return 'Remote BZR Branch'
746
def open(self, a_bzrdir):
747
assert isinstance(a_bzrdir, RemoteBzrDir)
748
return a_bzrdir.open_branch()
750
def initialize(self, a_bzrdir):
751
assert isinstance(a_bzrdir, RemoteBzrDir)
752
return a_bzrdir.create_branch()
755
class RemoteBranch(branch.Branch):
756
"""Branch stored on a server accessed by HPSS RPC.
758
At the moment most operations are mapped down to simple file operations.
761
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
763
"""Create a RemoteBranch instance.
765
:param real_branch: An optional local implementation of the branch
766
format, usually accessing the data via the VFS.
767
:param _client: Private parameter for testing.
769
# We intentionally don't call the parent class's __init__, because it
770
# will try to assign to self.tags, which is a property in this subclass.
771
# And the parent's __init__ doesn't do much anyway.
772
self._revision_history_cache = None
773
self.bzrdir = remote_bzrdir
774
if _client is not None:
775
self._client = _client
777
self._client = client._SmartClient(self.bzrdir._shared_medium)
778
self.repository = remote_repository
779
if real_branch is not None:
780
self._real_branch = real_branch
781
# Give the remote repository the matching real repo.
782
real_repo = self._real_branch.repository
783
if isinstance(real_repo, RemoteRepository):
784
real_repo._ensure_real()
785
real_repo = real_repo._real_repository
786
self.repository._set_real_repository(real_repo)
787
# Give the branch the remote repository to let fast-pathing happen.
788
self._real_branch.repository = self.repository
790
self._real_branch = None
791
# Fill out expected attributes of branch for bzrlib api users.
792
self._format = RemoteBranchFormat()
793
self.base = self.bzrdir.root_transport.base
794
self._control_files = None
795
self._lock_mode = None
796
self._lock_token = None
798
self._leave_lock = False
801
return "%s(%s)" % (self.__class__.__name__, self.base)
805
def _ensure_real(self):
806
"""Ensure that there is a _real_branch set.
808
Used before calls to self._real_branch.
810
if not self._real_branch:
811
assert vfs.vfs_enabled()
812
self.bzrdir._ensure_real()
813
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
814
# Give the remote repository the matching real repo.
815
real_repo = self._real_branch.repository
816
if isinstance(real_repo, RemoteRepository):
817
real_repo._ensure_real()
818
real_repo = real_repo._real_repository
819
self.repository._set_real_repository(real_repo)
820
# Give the branch the remote repository to let fast-pathing happen.
821
self._real_branch.repository = self.repository
822
# XXX: deal with _lock_mode == 'w'
823
if self._lock_mode == 'r':
824
self._real_branch.lock_read()
827
def control_files(self):
828
# Defer actually creating RemoteBranchLockableFiles until its needed,
829
# because it triggers an _ensure_real that we otherwise might not need.
830
if self._control_files is None:
831
self._control_files = RemoteBranchLockableFiles(
832
self.bzrdir, self._client)
833
return self._control_files
835
def _get_checkout_format(self):
837
return self._real_branch._get_checkout_format()
839
def get_physical_lock_status(self):
840
"""See Branch.get_physical_lock_status()."""
841
# should be an API call to the server, as branches must be lockable.
843
return self._real_branch.get_physical_lock_status()
846
if not self._lock_mode:
847
self._lock_mode = 'r'
849
if self._real_branch is not None:
850
self._real_branch.lock_read()
852
self._lock_count += 1
854
def _remote_lock_write(self, token):
856
branch_token = repo_token = ''
859
repo_token = self.repository.lock_write()
860
self.repository.unlock()
861
path = self.bzrdir._path_for_remote_call(self._client)
862
response = self._client.call('Branch.lock_write', path, branch_token,
864
if response[0] == 'ok':
865
ok, branch_token, repo_token = response
866
return branch_token, repo_token
867
elif response[0] == 'LockContention':
868
raise errors.LockContention('(remote lock)')
869
elif response[0] == 'TokenMismatch':
870
raise errors.TokenMismatch(token, '(remote token)')
871
elif response[0] == 'UnlockableTransport':
872
raise errors.UnlockableTransport(self.bzrdir.root_transport)
873
elif response[0] == 'ReadOnlyError':
874
raise errors.ReadOnlyError(self)
876
raise errors.UnexpectedSmartServerResponse(response)
878
def lock_write(self, token=None):
879
if not self._lock_mode:
880
remote_tokens = self._remote_lock_write(token)
881
self._lock_token, self._repo_lock_token = remote_tokens
882
assert self._lock_token, 'Remote server did not return a token!'
883
# TODO: We really, really, really don't want to call _ensure_real
884
# here, but it's the easiest way to ensure coherency between the
885
# state of the RemoteBranch and RemoteRepository objects and the
886
# physical locks. If we don't materialise the real objects here,
887
# then getting everything in the right state later is complex, so
888
# for now we just do it the lazy way.
889
# -- Andrew Bennetts, 2007-02-22.
891
if self._real_branch is not None:
892
self._real_branch.repository.lock_write(
893
token=self._repo_lock_token)
895
self._real_branch.lock_write(token=self._lock_token)
897
self._real_branch.repository.unlock()
898
if token is not None:
899
self._leave_lock = True
901
# XXX: this case seems to be unreachable; token cannot be None.
902
self._leave_lock = False
903
self._lock_mode = 'w'
905
elif self._lock_mode == 'r':
906
raise errors.ReadOnlyTransaction
908
if token is not None:
909
# A token was given to lock_write, and we're relocking, so check
910
# that the given token actually matches the one we already have.
911
if token != self._lock_token:
912
raise errors.TokenMismatch(token, self._lock_token)
913
self._lock_count += 1
914
return self._lock_token
916
def _unlock(self, branch_token, repo_token):
917
path = self.bzrdir._path_for_remote_call(self._client)
918
response = self._client.call('Branch.unlock', path, branch_token,
920
if response == ('ok',):
922
elif response[0] == 'TokenMismatch':
923
raise errors.TokenMismatch(
924
str((branch_token, repo_token)), '(remote tokens)')
926
raise errors.UnexpectedSmartServerResponse(response)
929
self._lock_count -= 1
930
if not self._lock_count:
931
self._clear_cached_state()
932
mode = self._lock_mode
933
self._lock_mode = None
934
if self._real_branch is not None:
935
if not self._leave_lock:
936
# If this RemoteBranch will remove the physical lock for the
937
# repository, make sure the _real_branch doesn't do it
938
# first. (Because the _real_branch's repository is set to
939
# be the RemoteRepository.)
940
self._real_branch.repository.leave_lock_in_place()
941
self._real_branch.unlock()
943
# Only write-locked branched need to make a remote method call
944
# to perfom the unlock.
946
assert self._lock_token, 'Locked, but no token!'
947
branch_token = self._lock_token
948
repo_token = self._repo_lock_token
949
self._lock_token = None
950
self._repo_lock_token = None
951
if not self._leave_lock:
952
self._unlock(branch_token, repo_token)
954
def break_lock(self):
956
return self._real_branch.break_lock()
958
def leave_lock_in_place(self):
959
self._leave_lock = True
961
def dont_leave_lock_in_place(self):
962
self._leave_lock = False
964
def last_revision_info(self):
965
"""See Branch.last_revision_info()."""
966
path = self.bzrdir._path_for_remote_call(self._client)
967
response = self._client.call('Branch.last_revision_info', path)
968
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
969
revno = int(response[1])
970
last_revision = response[2]
971
return (revno, last_revision)
973
def _gen_revision_history(self):
974
"""See Branch._gen_revision_history()."""
975
path = self.bzrdir._path_for_remote_call(self._client)
976
response = self._client.call_expecting_body(
977
'Branch.revision_history', path)
978
assert response[0][0] == 'ok', ('unexpected response code %s'
980
result = response[1].read_body_bytes().split('\x00')
986
def set_revision_history(self, rev_history):
987
# Send just the tip revision of the history; the server will generate
988
# the full history from that. If the revision doesn't exist in this
989
# branch, NoSuchRevision will be raised.
990
path = self.bzrdir._path_for_remote_call(self._client)
991
if rev_history == []:
994
rev_id = rev_history[-1]
995
self._clear_cached_state()
996
response = self._client.call('Branch.set_last_revision',
997
path, self._lock_token, self._repo_lock_token, rev_id)
998
if response[0] == 'NoSuchRevision':
999
raise NoSuchRevision(self, rev_id)
1001
assert response == ('ok',), (
1002
'unexpected response code %r' % (response,))
1003
self._cache_revision_history(rev_history)
1005
def get_parent(self):
1007
return self._real_branch.get_parent()
1009
def set_parent(self, url):
1011
return self._real_branch.set_parent(url)
1013
def get_config(self):
1014
return RemoteBranchConfig(self)
1016
def sprout(self, to_bzrdir, revision_id=None):
1017
# Like Branch.sprout, except that it sprouts a branch in the default
1018
# format, because RemoteBranches can't be created at arbitrary URLs.
1019
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1020
# to_bzrdir.create_branch...
1021
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1022
self.copy_content_into(result, revision_id=revision_id)
1023
result.set_parent(self.bzrdir.root_transport.base)
1027
def append_revision(self, *revision_ids):
1029
return self._real_branch.append_revision(*revision_ids)
1032
def pull(self, source, overwrite=False, stop_revision=None,
1034
# FIXME: This asks the real branch to run the hooks, which means
1035
# they're called with the wrong target branch parameter.
1036
# The test suite specifically allows this at present but it should be
1037
# fixed. It should get a _override_hook_target branch,
1038
# as push does. -- mbp 20070405
1040
self._real_branch.pull(
1041
source, overwrite=overwrite, stop_revision=stop_revision,
1045
def push(self, target, overwrite=False, stop_revision=None):
1047
return self._real_branch.push(
1048
target, overwrite=overwrite, stop_revision=stop_revision,
1049
_override_hook_source_branch=self)
1051
def is_locked(self):
1052
return self._lock_count >= 1
1054
def set_last_revision_info(self, revno, revision_id):
1056
self._clear_cached_state()
1057
return self._real_branch.set_last_revision_info(revno, revision_id)
1059
def generate_revision_history(self, revision_id, last_rev=None,
1062
return self._real_branch.generate_revision_history(
1063
revision_id, last_rev=last_rev, other_branch=other_branch)
1068
return self._real_branch.tags
1070
def set_push_location(self, location):
1072
return self._real_branch.set_push_location(location)
1074
def update_revisions(self, other, stop_revision=None):
1076
return self._real_branch.update_revisions(
1077
other, stop_revision=stop_revision)
1080
class RemoteBranchConfig(BranchConfig):
1083
self.branch._ensure_real()
1084
return self.branch._real_branch.get_config().username()
1086
def _get_branch_data_config(self):
1087
self.branch._ensure_real()
1088
if self._branch_data_config is None:
1089
self._branch_data_config = TreeConfig(self.branch._real_branch)
1090
return self._branch_data_config
1093
def _extract_tar(tar, to_dir):
1094
"""Extract all the contents of a tarfile object.
1096
A replacement for extractall, which is not present in python2.4
1099
tar.extract(tarinfo, to_dir)