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 has_same_location(self, other):
253
return (self.__class__ == other.__class__ and
254
self.bzrdir.transport.base == other.bzrdir.transport.base)
256
def _ensure_real(self):
257
"""Ensure that there is a _real_repository set.
259
Used before calls to self._real_repository.
261
if not self._real_repository:
262
self.bzrdir._ensure_real()
263
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
264
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
266
def get_revision_graph(self, revision_id=None):
267
"""See Repository.get_revision_graph()."""
268
if revision_id is None:
270
elif revision_id == NULL_REVISION:
273
path = self.bzrdir._path_for_remote_call(self._client)
274
assert type(revision_id) is str
275
response = self._client.call_expecting_body(
276
'Repository.get_revision_graph', path, revision_id)
277
if response[0][0] not in ['ok', 'nosuchrevision']:
278
raise errors.UnexpectedSmartServerResponse(response[0])
279
if response[0][0] == 'ok':
280
coded = response[1].read_body_bytes()
282
# no revisions in this repository!
284
lines = coded.split('\n')
287
d = tuple(line.split())
288
revision_graph[d[0]] = d[1:]
290
return revision_graph
292
response_body = response[1].read_body_bytes()
293
assert response_body == ''
294
raise NoSuchRevision(self, revision_id)
296
def has_revision(self, revision_id):
297
"""See Repository.has_revision()."""
298
if revision_id is None:
299
# The null revision is always present.
301
path = self.bzrdir._path_for_remote_call(self._client)
302
response = self._client.call('Repository.has_revision', path, revision_id)
303
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
304
return response[0] == 'yes'
306
def get_graph(self, other_repository=None):
307
"""Return the graph for this repository format"""
308
return self._real_repository.get_graph(other_repository)
310
def gather_stats(self, revid=None, committers=None):
311
"""See Repository.gather_stats()."""
312
path = self.bzrdir._path_for_remote_call(self._client)
313
if revid in (None, NULL_REVISION):
317
if committers is None or not committers:
318
fmt_committers = 'no'
320
fmt_committers = 'yes'
321
response = self._client.call_expecting_body(
322
'Repository.gather_stats', path, fmt_revid, fmt_committers)
323
assert response[0][0] == 'ok', \
324
'unexpected response code %s' % (response[0],)
326
body = response[1].read_body_bytes()
328
for line in body.split('\n'):
331
key, val_text = line.split(':')
332
if key in ('revisions', 'size', 'committers'):
333
result[key] = int(val_text)
334
elif key in ('firstrev', 'latestrev'):
335
values = val_text.split(' ')[1:]
336
result[key] = (float(values[0]), long(values[1]))
340
def get_physical_lock_status(self):
341
"""See Repository.get_physical_lock_status()."""
345
"""See Repository.is_shared()."""
346
path = self.bzrdir._path_for_remote_call(self._client)
347
response = self._client.call('Repository.is_shared', path)
348
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
349
return response[0] == 'yes'
352
# wrong eventually - want a local lock cache context
353
if not self._lock_mode:
354
self._lock_mode = 'r'
356
if self._real_repository is not None:
357
self._real_repository.lock_read()
359
self._lock_count += 1
361
def _remote_lock_write(self, token):
362
path = self.bzrdir._path_for_remote_call(self._client)
365
response = self._client.call('Repository.lock_write', path, token)
366
if response[0] == 'ok':
369
elif response[0] == 'LockContention':
370
raise errors.LockContention('(remote lock)')
371
elif response[0] == 'UnlockableTransport':
372
raise errors.UnlockableTransport(self.bzrdir.root_transport)
374
raise errors.UnexpectedSmartServerResponse(response)
376
def lock_write(self, token=None):
377
if not self._lock_mode:
378
self._lock_token = self._remote_lock_write(token)
379
assert self._lock_token, 'Remote server did not return a token!'
380
if self._real_repository is not None:
381
self._real_repository.lock_write(token=self._lock_token)
382
if token is not None:
383
self._leave_lock = True
385
self._leave_lock = False
386
self._lock_mode = 'w'
388
elif self._lock_mode == 'r':
389
raise errors.ReadOnlyError(self)
391
self._lock_count += 1
392
return self._lock_token
394
def leave_lock_in_place(self):
395
self._leave_lock = True
397
def dont_leave_lock_in_place(self):
398
self._leave_lock = False
400
def _set_real_repository(self, repository):
401
"""Set the _real_repository for this repository.
403
:param repository: The repository to fallback to for non-hpss
404
implemented operations.
406
assert not isinstance(repository, RemoteRepository)
407
self._real_repository = repository
408
if self._lock_mode == 'w':
409
# if we are already locked, the real repository must be able to
410
# acquire the lock with our token.
411
self._real_repository.lock_write(self._lock_token)
412
elif self._lock_mode == 'r':
413
self._real_repository.lock_read()
415
def _unlock(self, token):
416
path = self.bzrdir._path_for_remote_call(self._client)
417
response = self._client.call('Repository.unlock', path, token)
418
if response == ('ok',):
420
elif response[0] == 'TokenMismatch':
421
raise errors.TokenMismatch(token, '(remote token)')
423
raise errors.UnexpectedSmartServerResponse(response)
426
self._lock_count -= 1
427
if not self._lock_count:
428
mode = self._lock_mode
429
self._lock_mode = None
430
if self._real_repository is not None:
431
self._real_repository.unlock()
433
# Only write-locked repositories need to make a remote method
434
# call to perfom the unlock.
436
assert self._lock_token, 'Locked, but no token!'
437
token = self._lock_token
438
self._lock_token = None
439
if not self._leave_lock:
442
def break_lock(self):
443
# should hand off to the network
445
return self._real_repository.break_lock()
447
def _get_tarball(self, compression):
448
"""Return a TemporaryFile containing a repository tarball"""
450
path = self.bzrdir._path_for_remote_call(self._client)
451
response, protocol = self._client.call_expecting_body(
452
'Repository.tarball', path, compression)
453
assert response[0] in ('ok', 'failure'), \
454
'unexpected response code %s' % (response,)
455
if response[0] == 'ok':
456
# Extract the tarball and return it
457
t = tempfile.NamedTemporaryFile()
458
# TODO: rpc layer should read directly into it...
459
t.write(protocol.read_body_bytes())
463
raise errors.SmartServerError(error_code=response)
465
def sprout(self, to_bzrdir, revision_id=None):
466
# TODO: Option to control what format is created?
467
to_repo = to_bzrdir.create_repository()
468
self._copy_repository_tarball(to_repo, revision_id)
471
### These methods are just thin shims to the VFS object for now.
473
def revision_tree(self, revision_id):
475
return self._real_repository.revision_tree(revision_id)
477
def get_serializer_format(self):
479
return self._real_repository.get_serializer_format()
481
def get_commit_builder(self, branch, parents, config, timestamp=None,
482
timezone=None, committer=None, revprops=None,
484
# FIXME: It ought to be possible to call this without immediately
485
# triggering _ensure_real. For now it's the easiest thing to do.
487
builder = self._real_repository.get_commit_builder(branch, parents,
488
config, timestamp=timestamp, timezone=timezone,
489
committer=committer, revprops=revprops, revision_id=revision_id)
490
# Make the builder use this RemoteRepository rather than the real one.
491
builder.repository = self
495
def add_inventory(self, revid, inv, parents):
497
return self._real_repository.add_inventory(revid, inv, parents)
500
def add_revision(self, rev_id, rev, inv=None, config=None):
502
return self._real_repository.add_revision(
503
rev_id, rev, inv=inv, config=config)
506
def get_inventory(self, revision_id):
508
return self._real_repository.get_inventory(revision_id)
511
def get_revision(self, revision_id):
513
return self._real_repository.get_revision(revision_id)
516
def weave_store(self):
518
return self._real_repository.weave_store
520
def get_transaction(self):
522
return self._real_repository.get_transaction()
525
def clone(self, a_bzrdir, revision_id=None):
527
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
529
def make_working_trees(self):
530
"""RemoteRepositories never create working trees by default."""
533
def fetch(self, source, revision_id=None, pb=None):
535
return self._real_repository.fetch(
536
source, revision_id=revision_id, pb=pb)
538
def create_bundle(self, target, base, fileobj, format=None):
540
self._real_repository.create_bundle(target, base, fileobj, format)
543
def control_weaves(self):
545
return self._real_repository.control_weaves
548
def get_ancestry(self, revision_id, topo_sorted=True):
550
return self._real_repository.get_ancestry(revision_id, topo_sorted)
553
def get_inventory_weave(self):
555
return self._real_repository.get_inventory_weave()
557
def fileids_altered_by_revision_ids(self, revision_ids):
559
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
562
def get_signature_text(self, revision_id):
564
return self._real_repository.get_signature_text(revision_id)
567
def get_revision_graph_with_ghosts(self, revision_ids=None):
569
return self._real_repository.get_revision_graph_with_ghosts(
570
revision_ids=revision_ids)
573
def get_inventory_xml(self, revision_id):
575
return self._real_repository.get_inventory_xml(revision_id)
577
def deserialise_inventory(self, revision_id, xml):
579
return self._real_repository.deserialise_inventory(revision_id, xml)
581
def reconcile(self, other=None, thorough=False):
583
return self._real_repository.reconcile(other=other, thorough=thorough)
585
def all_revision_ids(self):
587
return self._real_repository.all_revision_ids()
590
def get_deltas_for_revisions(self, revisions):
592
return self._real_repository.get_deltas_for_revisions(revisions)
595
def get_revision_delta(self, revision_id):
597
return self._real_repository.get_revision_delta(revision_id)
600
def revision_trees(self, revision_ids):
602
return self._real_repository.revision_trees(revision_ids)
605
def get_revision_reconcile(self, revision_id):
607
return self._real_repository.get_revision_reconcile(revision_id)
610
def check(self, revision_ids):
612
return self._real_repository.check(revision_ids)
614
def copy_content_into(self, destination, revision_id=None):
616
return self._real_repository.copy_content_into(
617
destination, revision_id=revision_id)
619
def _copy_repository_tarball(self, destination, revision_id=None):
620
# get a tarball of the remote repository, and copy from that into the
622
from bzrlib import osutils
625
from StringIO import StringIO
626
# TODO: Maybe a progress bar while streaming the tarball?
627
note("Copying repository content as tarball...")
628
tar_file = self._get_tarball('bz2')
630
tar = tarfile.open('repository', fileobj=tar_file,
632
tmpdir = tempfile.mkdtemp()
634
_extract_tar(tar, tmpdir)
635
tmp_bzrdir = BzrDir.open(tmpdir)
636
tmp_repo = tmp_bzrdir.open_repository()
637
tmp_repo.copy_content_into(destination, revision_id)
639
osutils.rmtree(tmpdir)
642
# TODO: if the server doesn't support this operation, maybe do it the
643
# slow way using the _real_repository?
645
# TODO: Suggestion from john: using external tar is much faster than
646
# python's tarfile library, but it may not work on windows.
650
"""Compress the data within the repository.
652
This is not currently implemented within the smart server.
655
return self._real_repository.pack()
657
def set_make_working_trees(self, new_value):
658
raise NotImplementedError(self.set_make_working_trees)
661
def sign_revision(self, revision_id, gpg_strategy):
663
return self._real_repository.sign_revision(revision_id, gpg_strategy)
666
def get_revisions(self, revision_ids):
668
return self._real_repository.get_revisions(revision_ids)
670
def supports_rich_root(self):
672
return self._real_repository.supports_rich_root()
674
def iter_reverse_revision_history(self, revision_id):
676
return self._real_repository.iter_reverse_revision_history(revision_id)
679
def _serializer(self):
681
return self._real_repository._serializer
683
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
685
return self._real_repository.store_revision_signature(
686
gpg_strategy, plaintext, revision_id)
688
def has_signature_for_revision_id(self, revision_id):
690
return self._real_repository.has_signature_for_revision_id(revision_id)
693
class RemoteBranchLockableFiles(LockableFiles):
694
"""A 'LockableFiles' implementation that talks to a smart server.
696
This is not a public interface class.
699
def __init__(self, bzrdir, _client):
701
self._client = _client
702
self._need_find_modes = True
703
LockableFiles.__init__(
704
self, bzrdir.get_branch_transport(None),
705
'lock', lockdir.LockDir)
707
def _find_modes(self):
708
# RemoteBranches don't let the client set the mode of control files.
709
self._dir_mode = None
710
self._file_mode = None
713
"""'get' a remote path as per the LockableFiles interface.
715
:param path: the file to 'get'. If this is 'branch.conf', we do not
716
just retrieve a file, instead we ask the smart server to generate
717
a configuration for us - which is retrieved as an INI file.
719
if path == 'branch.conf':
720
path = self.bzrdir._path_for_remote_call(self._client)
721
response = self._client.call_expecting_body(
722
'Branch.get_config_file', path)
723
assert response[0][0] == 'ok', \
724
'unexpected response code %s' % (response[0],)
725
return StringIO(response[1].read_body_bytes())
728
return LockableFiles.get(self, path)
731
class RemoteBranchFormat(branch.BranchFormat):
733
def __eq__(self, other):
734
return (isinstance(other, RemoteBranchFormat) and
735
self.__dict__ == other.__dict__)
737
def get_format_description(self):
738
return 'Remote BZR Branch'
740
def get_format_string(self):
741
return 'Remote BZR Branch'
743
def open(self, a_bzrdir):
744
assert isinstance(a_bzrdir, RemoteBzrDir)
745
return a_bzrdir.open_branch()
747
def initialize(self, a_bzrdir):
748
assert isinstance(a_bzrdir, RemoteBzrDir)
749
return a_bzrdir.create_branch()
752
class RemoteBranch(branch.Branch):
753
"""Branch stored on a server accessed by HPSS RPC.
755
At the moment most operations are mapped down to simple file operations.
758
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
760
"""Create a RemoteBranch instance.
762
:param real_branch: An optional local implementation of the branch
763
format, usually accessing the data via the VFS.
764
:param _client: Private parameter for testing.
766
# We intentionally don't call the parent class's __init__, because it
767
# will try to assign to self.tags, which is a property in this subclass.
768
# And the parent's __init__ doesn't do much anyway.
769
self._revision_history_cache = None
770
self.bzrdir = remote_bzrdir
771
if _client is not None:
772
self._client = _client
774
self._client = client._SmartClient(self.bzrdir._shared_medium)
775
self.repository = remote_repository
776
if real_branch is not None:
777
self._real_branch = real_branch
778
# Give the remote repository the matching real repo.
779
real_repo = self._real_branch.repository
780
if isinstance(real_repo, RemoteRepository):
781
real_repo._ensure_real()
782
real_repo = real_repo._real_repository
783
self.repository._set_real_repository(real_repo)
784
# Give the branch the remote repository to let fast-pathing happen.
785
self._real_branch.repository = self.repository
787
self._real_branch = None
788
# Fill out expected attributes of branch for bzrlib api users.
789
self._format = RemoteBranchFormat()
790
self.base = self.bzrdir.root_transport.base
791
self._control_files = None
792
self._lock_mode = None
793
self._lock_token = None
795
self._leave_lock = False
798
return "%s(%s)" % (self.__class__.__name__, self.base)
802
def _ensure_real(self):
803
"""Ensure that there is a _real_branch set.
805
Used before calls to self._real_branch.
807
if not self._real_branch:
808
assert vfs.vfs_enabled()
809
self.bzrdir._ensure_real()
810
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
811
# Give the remote repository the matching real repo.
812
real_repo = self._real_branch.repository
813
if isinstance(real_repo, RemoteRepository):
814
real_repo._ensure_real()
815
real_repo = real_repo._real_repository
816
self.repository._set_real_repository(real_repo)
817
# Give the branch the remote repository to let fast-pathing happen.
818
self._real_branch.repository = self.repository
819
# XXX: deal with _lock_mode == 'w'
820
if self._lock_mode == 'r':
821
self._real_branch.lock_read()
824
def control_files(self):
825
# Defer actually creating RemoteBranchLockableFiles until its needed,
826
# because it triggers an _ensure_real that we otherwise might not need.
827
if self._control_files is None:
828
self._control_files = RemoteBranchLockableFiles(
829
self.bzrdir, self._client)
830
return self._control_files
832
def _get_checkout_format(self):
834
return self._real_branch._get_checkout_format()
836
def get_physical_lock_status(self):
837
"""See Branch.get_physical_lock_status()."""
838
# should be an API call to the server, as branches must be lockable.
840
return self._real_branch.get_physical_lock_status()
843
if not self._lock_mode:
844
self._lock_mode = 'r'
846
if self._real_branch is not None:
847
self._real_branch.lock_read()
849
self._lock_count += 1
851
def _remote_lock_write(self, token):
853
branch_token = repo_token = ''
856
repo_token = self.repository.lock_write()
857
self.repository.unlock()
858
path = self.bzrdir._path_for_remote_call(self._client)
859
response = self._client.call('Branch.lock_write', path, branch_token,
861
if response[0] == 'ok':
862
ok, branch_token, repo_token = response
863
return branch_token, repo_token
864
elif response[0] == 'LockContention':
865
raise errors.LockContention('(remote lock)')
866
elif response[0] == 'TokenMismatch':
867
raise errors.TokenMismatch(token, '(remote token)')
868
elif response[0] == 'UnlockableTransport':
869
raise errors.UnlockableTransport(self.bzrdir.root_transport)
870
elif response[0] == 'ReadOnlyError':
871
raise errors.ReadOnlyError(self)
873
raise errors.UnexpectedSmartServerResponse(response)
875
def lock_write(self, token=None):
876
if not self._lock_mode:
877
remote_tokens = self._remote_lock_write(token)
878
self._lock_token, self._repo_lock_token = remote_tokens
879
assert self._lock_token, 'Remote server did not return a token!'
880
# TODO: We really, really, really don't want to call _ensure_real
881
# here, but it's the easiest way to ensure coherency between the
882
# state of the RemoteBranch and RemoteRepository objects and the
883
# physical locks. If we don't materialise the real objects here,
884
# then getting everything in the right state later is complex, so
885
# for now we just do it the lazy way.
886
# -- Andrew Bennetts, 2007-02-22.
888
if self._real_branch is not None:
889
self._real_branch.repository.lock_write(
890
token=self._repo_lock_token)
892
self._real_branch.lock_write(token=self._lock_token)
894
self._real_branch.repository.unlock()
895
if token is not None:
896
self._leave_lock = True
898
# XXX: this case seems to be unreachable; token cannot be None.
899
self._leave_lock = False
900
self._lock_mode = 'w'
902
elif self._lock_mode == 'r':
903
raise errors.ReadOnlyTransaction
905
if token is not None:
906
# A token was given to lock_write, and we're relocking, so check
907
# that the given token actually matches the one we already have.
908
if token != self._lock_token:
909
raise errors.TokenMismatch(token, self._lock_token)
910
self._lock_count += 1
911
return self._lock_token
913
def _unlock(self, branch_token, repo_token):
914
path = self.bzrdir._path_for_remote_call(self._client)
915
response = self._client.call('Branch.unlock', path, branch_token,
917
if response == ('ok',):
919
elif response[0] == 'TokenMismatch':
920
raise errors.TokenMismatch(
921
str((branch_token, repo_token)), '(remote tokens)')
923
raise errors.UnexpectedSmartServerResponse(response)
926
self._lock_count -= 1
927
if not self._lock_count:
928
self._clear_cached_state()
929
mode = self._lock_mode
930
self._lock_mode = None
931
if self._real_branch is not None:
932
if not self._leave_lock:
933
# If this RemoteBranch will remove the physical lock for the
934
# repository, make sure the _real_branch doesn't do it
935
# first. (Because the _real_branch's repository is set to
936
# be the RemoteRepository.)
937
self._real_branch.repository.leave_lock_in_place()
938
self._real_branch.unlock()
940
# Only write-locked branched need to make a remote method call
941
# to perfom the unlock.
943
assert self._lock_token, 'Locked, but no token!'
944
branch_token = self._lock_token
945
repo_token = self._repo_lock_token
946
self._lock_token = None
947
self._repo_lock_token = None
948
if not self._leave_lock:
949
self._unlock(branch_token, repo_token)
951
def break_lock(self):
953
return self._real_branch.break_lock()
955
def leave_lock_in_place(self):
956
self._leave_lock = True
958
def dont_leave_lock_in_place(self):
959
self._leave_lock = False
961
def last_revision_info(self):
962
"""See Branch.last_revision_info()."""
963
path = self.bzrdir._path_for_remote_call(self._client)
964
response = self._client.call('Branch.last_revision_info', path)
965
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
966
revno = int(response[1])
967
last_revision = response[2]
968
return (revno, last_revision)
970
def _gen_revision_history(self):
971
"""See Branch._gen_revision_history()."""
972
path = self.bzrdir._path_for_remote_call(self._client)
973
response = self._client.call_expecting_body(
974
'Branch.revision_history', path)
975
assert response[0][0] == 'ok', ('unexpected response code %s'
977
result = response[1].read_body_bytes().split('\x00')
983
def set_revision_history(self, rev_history):
984
# Send just the tip revision of the history; the server will generate
985
# the full history from that. If the revision doesn't exist in this
986
# branch, NoSuchRevision will be raised.
987
path = self.bzrdir._path_for_remote_call(self._client)
988
if rev_history == []:
991
rev_id = rev_history[-1]
992
self._clear_cached_state()
993
response = self._client.call('Branch.set_last_revision',
994
path, self._lock_token, self._repo_lock_token, rev_id)
995
if response[0] == 'NoSuchRevision':
996
raise NoSuchRevision(self, rev_id)
998
assert response == ('ok',), (
999
'unexpected response code %r' % (response,))
1000
self._cache_revision_history(rev_history)
1002
def get_parent(self):
1004
return self._real_branch.get_parent()
1006
def set_parent(self, url):
1008
return self._real_branch.set_parent(url)
1010
def get_config(self):
1011
return RemoteBranchConfig(self)
1013
def sprout(self, to_bzrdir, revision_id=None):
1014
# Like Branch.sprout, except that it sprouts a branch in the default
1015
# format, because RemoteBranches can't be created at arbitrary URLs.
1016
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1017
# to_bzrdir.create_branch...
1018
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1019
self.copy_content_into(result, revision_id=revision_id)
1020
result.set_parent(self.bzrdir.root_transport.base)
1024
def append_revision(self, *revision_ids):
1026
return self._real_branch.append_revision(*revision_ids)
1029
def pull(self, source, overwrite=False, stop_revision=None,
1031
# FIXME: This asks the real branch to run the hooks, which means
1032
# they're called with the wrong target branch parameter.
1033
# The test suite specifically allows this at present but it should be
1034
# fixed. It should get a _override_hook_target branch,
1035
# as push does. -- mbp 20070405
1037
self._real_branch.pull(
1038
source, overwrite=overwrite, stop_revision=stop_revision,
1042
def push(self, target, overwrite=False, stop_revision=None):
1044
return self._real_branch.push(
1045
target, overwrite=overwrite, stop_revision=stop_revision,
1046
_override_hook_source_branch=self)
1048
def is_locked(self):
1049
return self._lock_count >= 1
1051
def set_last_revision_info(self, revno, revision_id):
1053
self._clear_cached_state()
1054
return self._real_branch.set_last_revision_info(revno, revision_id)
1056
def generate_revision_history(self, revision_id, last_rev=None,
1059
return self._real_branch.generate_revision_history(
1060
revision_id, last_rev=last_rev, other_branch=other_branch)
1065
return self._real_branch.tags
1067
def set_push_location(self, location):
1069
return self._real_branch.set_push_location(location)
1071
def update_revisions(self, other, stop_revision=None):
1073
return self._real_branch.update_revisions(
1074
other, stop_revision=stop_revision)
1077
class RemoteBranchConfig(BranchConfig):
1080
self.branch._ensure_real()
1081
return self.branch._real_branch.get_config().username()
1083
def _get_branch_data_config(self):
1084
self.branch._ensure_real()
1085
if self._branch_data_config is None:
1086
self._branch_data_config = TreeConfig(self.branch._real_branch)
1087
return self._branch_data_config
1090
def _extract_tar(tar, to_dir):
1091
"""Extract all the contents of a tarfile object.
1093
A replacement for extractall, which is not present in python2.4
1096
tar.extract(tarinfo, to_dir)