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.symbol_versioning import (
40
from bzrlib.trace import note
42
# Note: RemoteBzrDirFormat is in bzrdir.py
44
class RemoteBzrDir(BzrDir):
45
"""Control directory on a remote server, accessed via bzr:// or similar."""
47
def __init__(self, transport, _client=None):
48
"""Construct a RemoteBzrDir.
50
:param _client: Private parameter for testing. Disables probing and the
53
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
54
# this object holds a delegated bzrdir that uses file-level operations
55
# to talk to the other side
56
self._real_bzrdir = None
59
self._shared_medium = transport.get_shared_medium()
60
self._client = client._SmartClient(self._shared_medium)
62
self._client = _client
63
self._shared_medium = None
66
path = self._path_for_remote_call(self._client)
67
response = self._client.call('BzrDir.open', path)
68
if response not in [('yes',), ('no',)]:
69
raise errors.UnexpectedSmartServerResponse(response)
70
if response == ('no',):
71
raise errors.NotBranchError(path=transport.base)
73
def _ensure_real(self):
74
"""Ensure that there is a _real_bzrdir set.
76
Used before calls to self._real_bzrdir.
78
if not self._real_bzrdir:
79
self._real_bzrdir = BzrDir.open_from_transport(
80
self.root_transport, _server_formats=False)
82
def create_repository(self, shared=False):
84
self._real_bzrdir.create_repository(shared=shared)
85
return self.open_repository()
87
def create_branch(self):
89
real_branch = self._real_bzrdir.create_branch()
90
return RemoteBranch(self, self.find_repository(), real_branch)
92
def destroy_branch(self):
93
"""See BzrDir.destroy_branch"""
95
self._real_bzrdir.destroy_branch()
97
def create_workingtree(self, revision_id=None):
98
raise errors.NotLocalUrl(self.transport.base)
100
def find_branch_format(self):
101
"""Find the branch 'format' for this bzrdir.
103
This might be a synthetic object for e.g. RemoteBranch and SVN.
105
b = self.open_branch()
108
def get_branch_reference(self):
109
"""See BzrDir.get_branch_reference()."""
110
path = self._path_for_remote_call(self._client)
111
response = self._client.call('BzrDir.open_branch', path)
112
if response[0] == 'ok':
113
if response[1] == '':
114
# branch at this location.
117
# a branch reference, use the existing BranchReference logic.
119
elif response == ('nobranch',):
120
raise errors.NotBranchError(path=self.root_transport.base)
122
raise errors.UnexpectedSmartServerResponse(response)
124
def open_branch(self, _unsupported=False):
125
assert _unsupported == False, 'unsupported flag support not implemented yet.'
126
reference_url = self.get_branch_reference()
127
if reference_url is None:
128
# branch at this location.
129
return RemoteBranch(self, self.find_repository())
131
# a branch reference, use the existing BranchReference logic.
132
format = BranchReferenceFormat()
133
return format.open(self, _found=True, location=reference_url)
135
def open_repository(self):
136
path = self._path_for_remote_call(self._client)
137
response = self._client.call('BzrDir.find_repository', path)
138
assert response[0] in ('ok', 'norepository'), \
139
'unexpected response code %s' % (response,)
140
if response[0] == 'norepository':
141
raise errors.NoRepositoryPresent(self)
142
assert len(response) == 4, 'incorrect response length %s' % (response,)
143
if response[1] == '':
144
format = RemoteRepositoryFormat()
145
format.rich_root_data = (response[2] == 'yes')
146
format.supports_tree_reference = (response[3] == 'yes')
147
return RemoteRepository(self, format)
149
raise errors.NoRepositoryPresent(self)
151
def open_workingtree(self, recommend_upgrade=True):
153
if self._real_bzrdir.has_workingtree():
154
raise errors.NotLocalUrl(self.root_transport)
156
raise errors.NoWorkingTree(self.root_transport.base)
158
def _path_for_remote_call(self, client):
159
"""Return the path to be used for this bzrdir in a remote call."""
160
return client.remote_path_from_transport(self.root_transport)
162
def get_branch_transport(self, branch_format):
164
return self._real_bzrdir.get_branch_transport(branch_format)
166
def get_repository_transport(self, repository_format):
168
return self._real_bzrdir.get_repository_transport(repository_format)
170
def get_workingtree_transport(self, workingtree_format):
172
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
174
def can_convert_format(self):
175
"""Upgrading of remote bzrdirs is not supported yet."""
178
def needs_format_conversion(self, format=None):
179
"""Upgrading of remote bzrdirs is not supported yet."""
182
def clone(self, url, revision_id=None, force_new_repo=False):
184
return self._real_bzrdir.clone(url, revision_id=revision_id,
185
force_new_repo=force_new_repo)
188
class RemoteRepositoryFormat(repository.RepositoryFormat):
189
"""Format for repositories accessed over a _SmartClient.
191
Instances of this repository are represented by RemoteRepository
194
The RemoteRepositoryFormat is parameterised during construction
195
to reflect the capabilities of the real, remote format. Specifically
196
the attributes rich_root_data and supports_tree_reference are set
197
on a per instance basis, and are not set (and should not be) at
201
_matchingbzrdir = RemoteBzrDirFormat
203
def initialize(self, a_bzrdir, shared=False):
204
assert isinstance(a_bzrdir, RemoteBzrDir), \
205
'%r is not a RemoteBzrDir' % (a_bzrdir,)
206
return a_bzrdir.create_repository(shared=shared)
208
def open(self, a_bzrdir):
209
assert isinstance(a_bzrdir, RemoteBzrDir)
210
return a_bzrdir.open_repository()
212
def get_format_description(self):
213
return 'bzr remote repository'
215
def __eq__(self, other):
216
return self.__class__ == other.__class__
218
def check_conversion_target(self, target_format):
219
if self.rich_root_data and not target_format.rich_root_data:
220
raise errors.BadConversionTarget(
221
'Does not support rich root data.', target_format)
222
if (self.supports_tree_reference and
223
not getattr(target_format, 'supports_tree_reference', False)):
224
raise errors.BadConversionTarget(
225
'Does not support nested trees', target_format)
228
class RemoteRepository(object):
229
"""Repository accessed over rpc.
231
For the moment most operations are performed using local transport-backed
235
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
236
"""Create a RemoteRepository instance.
238
:param remote_bzrdir: The bzrdir hosting this repository.
239
:param format: The RemoteFormat object to use.
240
:param real_repository: If not None, a local implementation of the
241
repository logic for the repository, usually accessing the data
243
:param _client: Private testing parameter - override the smart client
244
to be used by the repository.
247
self._real_repository = real_repository
249
self._real_repository = None
250
self.bzrdir = remote_bzrdir
252
self._client = client._SmartClient(self.bzrdir._shared_medium)
254
self._client = _client
255
self._format = format
256
self._lock_mode = None
257
self._lock_token = None
259
self._leave_lock = False
261
self._reconcile_does_inventory_gc = True
263
def abort_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.abort_write_group()
274
def commit_write_group(self):
275
"""Complete a write group on the decorated repository.
277
Smart methods peform operations in a single step so this api
278
is not really applicable except as a compatibility thunk
279
for older plugins that don't use e.g. the CommitBuilder
283
return self._real_repository.commit_write_group()
285
def _ensure_real(self):
286
"""Ensure that there is a _real_repository set.
288
Used before calls to self._real_repository.
290
if not self._real_repository:
291
self.bzrdir._ensure_real()
292
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
293
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
295
def get_revision_graph(self, revision_id=None):
296
"""See Repository.get_revision_graph()."""
297
if revision_id is None:
299
elif revision_id == NULL_REVISION:
302
path = self.bzrdir._path_for_remote_call(self._client)
303
assert type(revision_id) is str
304
response = self._client.call_expecting_body(
305
'Repository.get_revision_graph', path, revision_id)
306
if response[0][0] not in ['ok', 'nosuchrevision']:
307
raise errors.UnexpectedSmartServerResponse(response[0])
308
if response[0][0] == 'ok':
309
coded = response[1].read_body_bytes()
311
# no revisions in this repository!
313
lines = coded.split('\n')
316
d = tuple(line.split())
317
revision_graph[d[0]] = d[1:]
319
return revision_graph
321
response_body = response[1].read_body_bytes()
322
assert response_body == ''
323
raise NoSuchRevision(self, revision_id)
325
def has_revision(self, revision_id):
326
"""See Repository.has_revision()."""
327
if revision_id is None:
328
# The null revision is always present.
330
path = self.bzrdir._path_for_remote_call(self._client)
331
response = self._client.call('Repository.has_revision', path, revision_id)
332
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
333
return response[0] == 'yes'
335
def has_same_location(self, other):
336
return (self.__class__ == other.__class__ and
337
self.bzrdir.transport.base == other.bzrdir.transport.base)
339
def get_graph(self, other_repository=None):
340
"""Return the graph for this repository format"""
341
return self._real_repository.get_graph(other_repository)
343
def gather_stats(self, revid=None, committers=None):
344
"""See Repository.gather_stats()."""
345
path = self.bzrdir._path_for_remote_call(self._client)
346
if revid in (None, NULL_REVISION):
350
if committers is None or not committers:
351
fmt_committers = 'no'
353
fmt_committers = 'yes'
354
response = self._client.call_expecting_body(
355
'Repository.gather_stats', path, fmt_revid, fmt_committers)
356
assert response[0][0] == 'ok', \
357
'unexpected response code %s' % (response[0],)
359
body = response[1].read_body_bytes()
361
for line in body.split('\n'):
364
key, val_text = line.split(':')
365
if key in ('revisions', 'size', 'committers'):
366
result[key] = int(val_text)
367
elif key in ('firstrev', 'latestrev'):
368
values = val_text.split(' ')[1:]
369
result[key] = (float(values[0]), long(values[1]))
373
def get_physical_lock_status(self):
374
"""See Repository.get_physical_lock_status()."""
377
def is_in_write_group(self):
378
"""Return True if there is an open write group.
380
write groups are only applicable locally for the smart server..
382
if self._real_repository:
383
return self._real_repository.is_in_write_group()
386
return self._lock_count >= 1
389
"""See Repository.is_shared()."""
390
path = self.bzrdir._path_for_remote_call(self._client)
391
response = self._client.call('Repository.is_shared', path)
392
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
393
return response[0] == 'yes'
396
# wrong eventually - want a local lock cache context
397
if not self._lock_mode:
398
self._lock_mode = 'r'
400
if self._real_repository is not None:
401
self._real_repository.lock_read()
403
self._lock_count += 1
405
def _remote_lock_write(self, token):
406
path = self.bzrdir._path_for_remote_call(self._client)
409
response = self._client.call('Repository.lock_write', path, token)
410
if response[0] == 'ok':
413
elif response[0] == 'LockContention':
414
raise errors.LockContention('(remote lock)')
415
elif response[0] == 'UnlockableTransport':
416
raise errors.UnlockableTransport(self.bzrdir.root_transport)
417
elif response[0] == 'LockFailed':
418
raise errors.LockFailed(response[1], response[2])
420
raise errors.UnexpectedSmartServerResponse(response)
422
def lock_write(self, token=None):
423
if not self._lock_mode:
424
self._lock_token = self._remote_lock_write(token)
425
assert self._lock_token, 'Remote server did not return a token!'
426
if self._real_repository is not None:
427
self._real_repository.lock_write(token=self._lock_token)
428
if token is not None:
429
self._leave_lock = True
431
self._leave_lock = False
432
self._lock_mode = 'w'
434
elif self._lock_mode == 'r':
435
raise errors.ReadOnlyError(self)
437
self._lock_count += 1
438
return self._lock_token
440
def leave_lock_in_place(self):
441
self._leave_lock = True
443
def dont_leave_lock_in_place(self):
444
self._leave_lock = False
446
def _set_real_repository(self, repository):
447
"""Set the _real_repository for this repository.
449
:param repository: The repository to fallback to for non-hpss
450
implemented operations.
452
assert not isinstance(repository, RemoteRepository)
453
self._real_repository = repository
454
if self._lock_mode == 'w':
455
# if we are already locked, the real repository must be able to
456
# acquire the lock with our token.
457
self._real_repository.lock_write(self._lock_token)
458
elif self._lock_mode == 'r':
459
self._real_repository.lock_read()
461
def start_write_group(self):
462
"""Start a write group on the decorated repository.
464
Smart methods peform operations in a single step so this api
465
is not really applicable except as a compatibility thunk
466
for older plugins that don't use e.g. the CommitBuilder
470
return self._real_repository.start_write_group()
472
def _unlock(self, token):
473
path = self.bzrdir._path_for_remote_call(self._client)
474
response = self._client.call('Repository.unlock', path, token)
475
if response == ('ok',):
477
elif response[0] == 'TokenMismatch':
478
raise errors.TokenMismatch(token, '(remote token)')
480
raise errors.UnexpectedSmartServerResponse(response)
483
if self._lock_count == 1 and self._lock_mode == 'w':
484
# don't unlock if inside a write group.
485
if self.is_in_write_group():
486
raise errors.BzrError(
487
'Must end write groups before releasing write locks.')
488
self._lock_count -= 1
489
if not self._lock_count:
490
mode = self._lock_mode
491
self._lock_mode = None
492
if self._real_repository is not None:
493
self._real_repository.unlock()
495
# Only write-locked repositories need to make a remote method
496
# call to perfom the unlock.
498
assert self._lock_token, 'Locked, but no token!'
499
token = self._lock_token
500
self._lock_token = None
501
if not self._leave_lock:
504
def break_lock(self):
505
# should hand off to the network
507
return self._real_repository.break_lock()
509
def _get_tarball(self, compression):
510
"""Return a TemporaryFile containing a repository tarball"""
512
path = self.bzrdir._path_for_remote_call(self._client)
513
response, protocol = self._client.call_expecting_body(
514
'Repository.tarball', path, compression)
515
assert response[0] in ('ok', 'failure'), \
516
'unexpected response code %s' % (response,)
517
if response[0] == 'ok':
518
# Extract the tarball and return it
519
t = tempfile.NamedTemporaryFile()
520
# TODO: rpc layer should read directly into it...
521
t.write(protocol.read_body_bytes())
525
raise errors.SmartServerError(error_code=response)
527
def sprout(self, to_bzrdir, revision_id=None):
528
# TODO: Option to control what format is created?
529
to_repo = to_bzrdir.create_repository()
530
self._copy_repository_tarball(to_repo, revision_id)
533
### These methods are just thin shims to the VFS object for now.
535
def revision_tree(self, revision_id):
537
return self._real_repository.revision_tree(revision_id)
539
def get_serializer_format(self):
541
return self._real_repository.get_serializer_format()
543
def get_commit_builder(self, branch, parents, config, timestamp=None,
544
timezone=None, committer=None, revprops=None,
546
# FIXME: It ought to be possible to call this without immediately
547
# triggering _ensure_real. For now it's the easiest thing to do.
549
builder = self._real_repository.get_commit_builder(branch, parents,
550
config, timestamp=timestamp, timezone=timezone,
551
committer=committer, revprops=revprops, revision_id=revision_id)
552
# Make the builder use this RemoteRepository rather than the real one.
553
builder.repository = self
557
def add_inventory(self, revid, inv, parents):
559
return self._real_repository.add_inventory(revid, inv, parents)
562
def add_revision(self, rev_id, rev, inv=None, config=None):
564
return self._real_repository.add_revision(
565
rev_id, rev, inv=inv, config=config)
568
def get_inventory(self, revision_id):
570
return self._real_repository.get_inventory(revision_id)
573
def get_revision(self, revision_id):
575
return self._real_repository.get_revision(revision_id)
578
def weave_store(self):
580
return self._real_repository.weave_store
582
def get_transaction(self):
584
return self._real_repository.get_transaction()
587
def clone(self, a_bzrdir, revision_id=None):
589
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
591
def make_working_trees(self):
592
"""RemoteRepositories never create working trees by default."""
595
def fetch(self, source, revision_id=None, pb=None):
597
return self._real_repository.fetch(
598
source, revision_id=revision_id, pb=pb)
600
def create_bundle(self, target, base, fileobj, format=None):
602
self._real_repository.create_bundle(target, base, fileobj, format)
605
def control_weaves(self):
607
return self._real_repository.control_weaves
610
def get_ancestry(self, revision_id, topo_sorted=True):
612
return self._real_repository.get_ancestry(revision_id, topo_sorted)
615
def get_inventory_weave(self):
617
return self._real_repository.get_inventory_weave()
619
def fileids_altered_by_revision_ids(self, revision_ids):
621
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
623
def iter_files_bytes(self, desired_files):
624
"""See Repository.iter_file_bytes.
627
return self._real_repository.iter_files_bytes(desired_files)
630
def get_signature_text(self, revision_id):
632
return self._real_repository.get_signature_text(revision_id)
635
def get_revision_graph_with_ghosts(self, revision_ids=None):
637
return self._real_repository.get_revision_graph_with_ghosts(
638
revision_ids=revision_ids)
641
def get_inventory_xml(self, revision_id):
643
return self._real_repository.get_inventory_xml(revision_id)
645
def deserialise_inventory(self, revision_id, xml):
647
return self._real_repository.deserialise_inventory(revision_id, xml)
649
def reconcile(self, other=None, thorough=False):
651
return self._real_repository.reconcile(other=other, thorough=thorough)
653
def all_revision_ids(self):
655
return self._real_repository.all_revision_ids()
658
def get_deltas_for_revisions(self, revisions):
660
return self._real_repository.get_deltas_for_revisions(revisions)
663
def get_revision_delta(self, revision_id):
665
return self._real_repository.get_revision_delta(revision_id)
668
def revision_trees(self, revision_ids):
670
return self._real_repository.revision_trees(revision_ids)
673
def get_revision_reconcile(self, revision_id):
675
return self._real_repository.get_revision_reconcile(revision_id)
678
def check(self, revision_ids):
680
return self._real_repository.check(revision_ids)
682
def copy_content_into(self, destination, revision_id=None):
684
return self._real_repository.copy_content_into(
685
destination, revision_id=revision_id)
687
def _copy_repository_tarball(self, destination, revision_id=None):
688
# get a tarball of the remote repository, and copy from that into the
690
from bzrlib import osutils
693
from StringIO import StringIO
694
# TODO: Maybe a progress bar while streaming the tarball?
695
note("Copying repository content as tarball...")
696
tar_file = self._get_tarball('bz2')
698
tar = tarfile.open('repository', fileobj=tar_file,
700
tmpdir = tempfile.mkdtemp()
702
_extract_tar(tar, tmpdir)
703
tmp_bzrdir = BzrDir.open(tmpdir)
704
tmp_repo = tmp_bzrdir.open_repository()
705
tmp_repo.copy_content_into(destination, revision_id)
707
osutils.rmtree(tmpdir)
710
# TODO: if the server doesn't support this operation, maybe do it the
711
# slow way using the _real_repository?
713
# TODO: Suggestion from john: using external tar is much faster than
714
# python's tarfile library, but it may not work on windows.
718
"""Compress the data within the repository.
720
This is not currently implemented within the smart server.
723
return self._real_repository.pack()
725
def set_make_working_trees(self, new_value):
726
raise NotImplementedError(self.set_make_working_trees)
729
def sign_revision(self, revision_id, gpg_strategy):
731
return self._real_repository.sign_revision(revision_id, gpg_strategy)
734
def get_revisions(self, revision_ids):
736
return self._real_repository.get_revisions(revision_ids)
738
def supports_rich_root(self):
740
return self._real_repository.supports_rich_root()
742
def iter_reverse_revision_history(self, revision_id):
744
return self._real_repository.iter_reverse_revision_history(revision_id)
747
def _serializer(self):
749
return self._real_repository._serializer
751
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
753
return self._real_repository.store_revision_signature(
754
gpg_strategy, plaintext, revision_id)
756
def has_signature_for_revision_id(self, revision_id):
758
return self._real_repository.has_signature_for_revision_id(revision_id)
761
class RemoteBranchLockableFiles(LockableFiles):
762
"""A 'LockableFiles' implementation that talks to a smart server.
764
This is not a public interface class.
767
def __init__(self, bzrdir, _client):
769
self._client = _client
770
self._need_find_modes = True
771
LockableFiles.__init__(
772
self, bzrdir.get_branch_transport(None),
773
'lock', lockdir.LockDir)
775
def _find_modes(self):
776
# RemoteBranches don't let the client set the mode of control files.
777
self._dir_mode = None
778
self._file_mode = None
781
"""'get' a remote path as per the LockableFiles interface.
783
:param path: the file to 'get'. If this is 'branch.conf', we do not
784
just retrieve a file, instead we ask the smart server to generate
785
a configuration for us - which is retrieved as an INI file.
787
if path == 'branch.conf':
788
path = self.bzrdir._path_for_remote_call(self._client)
789
response = self._client.call_expecting_body(
790
'Branch.get_config_file', path)
791
assert response[0][0] == 'ok', \
792
'unexpected response code %s' % (response[0],)
793
return StringIO(response[1].read_body_bytes())
796
return LockableFiles.get(self, path)
799
class RemoteBranchFormat(branch.BranchFormat):
801
def __eq__(self, other):
802
return (isinstance(other, RemoteBranchFormat) and
803
self.__dict__ == other.__dict__)
805
def get_format_description(self):
806
return 'Remote BZR Branch'
808
def get_format_string(self):
809
return 'Remote BZR Branch'
811
def open(self, a_bzrdir):
812
assert isinstance(a_bzrdir, RemoteBzrDir)
813
return a_bzrdir.open_branch()
815
def initialize(self, a_bzrdir):
816
assert isinstance(a_bzrdir, RemoteBzrDir)
817
return a_bzrdir.create_branch()
819
def supports_tags(self):
820
# Remote branches might support tags, but we won't know until we
821
# access the real remote branch.
825
class RemoteBranch(branch.Branch):
826
"""Branch stored on a server accessed by HPSS RPC.
828
At the moment most operations are mapped down to simple file operations.
831
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
833
"""Create a RemoteBranch instance.
835
:param real_branch: An optional local implementation of the branch
836
format, usually accessing the data via the VFS.
837
:param _client: Private parameter for testing.
839
# We intentionally don't call the parent class's __init__, because it
840
# will try to assign to self.tags, which is a property in this subclass.
841
# And the parent's __init__ doesn't do much anyway.
842
self._revision_history_cache = None
843
self.bzrdir = remote_bzrdir
844
if _client is not None:
845
self._client = _client
847
self._client = client._SmartClient(self.bzrdir._shared_medium)
848
self.repository = remote_repository
849
if real_branch is not None:
850
self._real_branch = real_branch
851
# Give the remote repository the matching real repo.
852
real_repo = self._real_branch.repository
853
if isinstance(real_repo, RemoteRepository):
854
real_repo._ensure_real()
855
real_repo = real_repo._real_repository
856
self.repository._set_real_repository(real_repo)
857
# Give the branch the remote repository to let fast-pathing happen.
858
self._real_branch.repository = self.repository
860
self._real_branch = None
861
# Fill out expected attributes of branch for bzrlib api users.
862
self._format = RemoteBranchFormat()
863
self.base = self.bzrdir.root_transport.base
864
self._control_files = None
865
self._lock_mode = None
866
self._lock_token = None
868
self._leave_lock = False
871
return "%s(%s)" % (self.__class__.__name__, self.base)
875
def _ensure_real(self):
876
"""Ensure that there is a _real_branch set.
878
Used before calls to self._real_branch.
880
if not self._real_branch:
881
assert vfs.vfs_enabled()
882
self.bzrdir._ensure_real()
883
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
884
# Give the remote repository the matching real repo.
885
real_repo = self._real_branch.repository
886
if isinstance(real_repo, RemoteRepository):
887
real_repo._ensure_real()
888
real_repo = real_repo._real_repository
889
self.repository._set_real_repository(real_repo)
890
# Give the branch the remote repository to let fast-pathing happen.
891
self._real_branch.repository = self.repository
892
# XXX: deal with _lock_mode == 'w'
893
if self._lock_mode == 'r':
894
self._real_branch.lock_read()
897
def control_files(self):
898
# Defer actually creating RemoteBranchLockableFiles until its needed,
899
# because it triggers an _ensure_real that we otherwise might not need.
900
if self._control_files is None:
901
self._control_files = RemoteBranchLockableFiles(
902
self.bzrdir, self._client)
903
return self._control_files
905
def _get_checkout_format(self):
907
return self._real_branch._get_checkout_format()
909
def get_physical_lock_status(self):
910
"""See Branch.get_physical_lock_status()."""
911
# should be an API call to the server, as branches must be lockable.
913
return self._real_branch.get_physical_lock_status()
916
if not self._lock_mode:
917
self._lock_mode = 'r'
919
if self._real_branch is not None:
920
self._real_branch.lock_read()
922
self._lock_count += 1
924
def _remote_lock_write(self, token):
926
branch_token = repo_token = ''
929
repo_token = self.repository.lock_write()
930
self.repository.unlock()
931
path = self.bzrdir._path_for_remote_call(self._client)
932
response = self._client.call('Branch.lock_write', path, branch_token,
934
if response[0] == 'ok':
935
ok, branch_token, repo_token = response
936
return branch_token, repo_token
937
elif response[0] == 'LockContention':
938
raise errors.LockContention('(remote lock)')
939
elif response[0] == 'TokenMismatch':
940
raise errors.TokenMismatch(token, '(remote token)')
941
elif response[0] == 'UnlockableTransport':
942
raise errors.UnlockableTransport(self.bzrdir.root_transport)
943
elif response[0] == 'ReadOnlyError':
944
raise errors.ReadOnlyError(self)
945
elif response[0] == 'LockFailed':
946
raise errors.LockFailed(response[1], response[2])
948
raise errors.UnexpectedSmartServerResponse(response)
950
def lock_write(self, token=None):
951
if not self._lock_mode:
952
remote_tokens = self._remote_lock_write(token)
953
self._lock_token, self._repo_lock_token = remote_tokens
954
assert self._lock_token, 'Remote server did not return a token!'
955
# TODO: We really, really, really don't want to call _ensure_real
956
# here, but it's the easiest way to ensure coherency between the
957
# state of the RemoteBranch and RemoteRepository objects and the
958
# physical locks. If we don't materialise the real objects here,
959
# then getting everything in the right state later is complex, so
960
# for now we just do it the lazy way.
961
# -- Andrew Bennetts, 2007-02-22.
963
if self._real_branch is not None:
964
self._real_branch.repository.lock_write(
965
token=self._repo_lock_token)
967
self._real_branch.lock_write(token=self._lock_token)
969
self._real_branch.repository.unlock()
970
if token is not None:
971
self._leave_lock = True
973
# XXX: this case seems to be unreachable; token cannot be None.
974
self._leave_lock = False
975
self._lock_mode = 'w'
977
elif self._lock_mode == 'r':
978
raise errors.ReadOnlyTransaction
980
if token is not None:
981
# A token was given to lock_write, and we're relocking, so check
982
# that the given token actually matches the one we already have.
983
if token != self._lock_token:
984
raise errors.TokenMismatch(token, self._lock_token)
985
self._lock_count += 1
986
return self._lock_token
988
def _unlock(self, branch_token, repo_token):
989
path = self.bzrdir._path_for_remote_call(self._client)
990
response = self._client.call('Branch.unlock', path, branch_token,
992
if response == ('ok',):
994
elif response[0] == 'TokenMismatch':
995
raise errors.TokenMismatch(
996
str((branch_token, repo_token)), '(remote tokens)')
998
raise errors.UnexpectedSmartServerResponse(response)
1001
self._lock_count -= 1
1002
if not self._lock_count:
1003
self._clear_cached_state()
1004
mode = self._lock_mode
1005
self._lock_mode = None
1006
if self._real_branch is not None:
1007
if not self._leave_lock:
1008
# If this RemoteBranch will remove the physical lock for the
1009
# repository, make sure the _real_branch doesn't do it
1010
# first. (Because the _real_branch's repository is set to
1011
# be the RemoteRepository.)
1012
self._real_branch.repository.leave_lock_in_place()
1013
self._real_branch.unlock()
1015
# Only write-locked branched need to make a remote method call
1016
# to perfom the unlock.
1018
assert self._lock_token, 'Locked, but no token!'
1019
branch_token = self._lock_token
1020
repo_token = self._repo_lock_token
1021
self._lock_token = None
1022
self._repo_lock_token = None
1023
if not self._leave_lock:
1024
self._unlock(branch_token, repo_token)
1026
def break_lock(self):
1028
return self._real_branch.break_lock()
1030
def leave_lock_in_place(self):
1031
self._leave_lock = True
1033
def dont_leave_lock_in_place(self):
1034
self._leave_lock = False
1036
def last_revision_info(self):
1037
"""See Branch.last_revision_info()."""
1038
path = self.bzrdir._path_for_remote_call(self._client)
1039
response = self._client.call('Branch.last_revision_info', path)
1040
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1041
revno = int(response[1])
1042
last_revision = response[2]
1043
return (revno, last_revision)
1045
def _gen_revision_history(self):
1046
"""See Branch._gen_revision_history()."""
1047
path = self.bzrdir._path_for_remote_call(self._client)
1048
response = self._client.call_expecting_body(
1049
'Branch.revision_history', path)
1050
assert response[0][0] == 'ok', ('unexpected response code %s'
1052
result = response[1].read_body_bytes().split('\x00')
1058
def set_revision_history(self, rev_history):
1059
# Send just the tip revision of the history; the server will generate
1060
# the full history from that. If the revision doesn't exist in this
1061
# branch, NoSuchRevision will be raised.
1062
path = self.bzrdir._path_for_remote_call(self._client)
1063
if rev_history == []:
1066
rev_id = rev_history[-1]
1067
self._clear_cached_state()
1068
response = self._client.call('Branch.set_last_revision',
1069
path, self._lock_token, self._repo_lock_token, rev_id)
1070
if response[0] == 'NoSuchRevision':
1071
raise NoSuchRevision(self, rev_id)
1073
assert response == ('ok',), (
1074
'unexpected response code %r' % (response,))
1075
self._cache_revision_history(rev_history)
1077
def get_parent(self):
1079
return self._real_branch.get_parent()
1081
def set_parent(self, url):
1083
return self._real_branch.set_parent(url)
1085
def get_config(self):
1086
return RemoteBranchConfig(self)
1088
def sprout(self, to_bzrdir, revision_id=None):
1089
# Like Branch.sprout, except that it sprouts a branch in the default
1090
# format, because RemoteBranches can't be created at arbitrary URLs.
1091
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1092
# to_bzrdir.create_branch...
1093
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1094
self.copy_content_into(result, revision_id=revision_id)
1095
result.set_parent(self.bzrdir.root_transport.base)
1099
def pull(self, source, overwrite=False, stop_revision=None,
1101
# FIXME: This asks the real branch to run the hooks, which means
1102
# they're called with the wrong target branch parameter.
1103
# The test suite specifically allows this at present but it should be
1104
# fixed. It should get a _override_hook_target branch,
1105
# as push does. -- mbp 20070405
1107
self._real_branch.pull(
1108
source, overwrite=overwrite, stop_revision=stop_revision,
1112
def push(self, target, overwrite=False, stop_revision=None):
1114
return self._real_branch.push(
1115
target, overwrite=overwrite, stop_revision=stop_revision,
1116
_override_hook_source_branch=self)
1118
def is_locked(self):
1119
return self._lock_count >= 1
1121
def set_last_revision_info(self, revno, revision_id):
1123
self._clear_cached_state()
1124
return self._real_branch.set_last_revision_info(revno, revision_id)
1126
def generate_revision_history(self, revision_id, last_rev=None,
1129
return self._real_branch.generate_revision_history(
1130
revision_id, last_rev=last_rev, other_branch=other_branch)
1135
return self._real_branch.tags
1137
def set_push_location(self, location):
1139
return self._real_branch.set_push_location(location)
1141
def update_revisions(self, other, stop_revision=None):
1143
return self._real_branch.update_revisions(
1144
other, stop_revision=stop_revision)
1147
class RemoteBranchConfig(BranchConfig):
1150
self.branch._ensure_real()
1151
return self.branch._real_branch.get_config().username()
1153
def _get_branch_data_config(self):
1154
self.branch._ensure_real()
1155
if self._branch_data_config is None:
1156
self._branch_data_config = TreeConfig(self.branch._real_branch)
1157
return self._branch_data_config
1160
def _extract_tar(tar, to_dir):
1161
"""Extract all the contents of a tarfile object.
1163
A replacement for extractall, which is not present in python2.4
1166
tar.extract(tarinfo, to_dir)