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):
94
self._real_bzrdir.destroy_branch()
96
def create_workingtree(self, revision_id=None):
97
raise errors.NotLocalUrl(self.transport.base)
99
def find_branch_format(self):
100
"""Find the branch 'format' for this bzrdir.
102
This might be a synthetic object for e.g. RemoteBranch and SVN.
104
b = self.open_branch()
107
def get_branch_reference(self):
108
"""See BzrDir.get_branch_reference()."""
109
path = self._path_for_remote_call(self._client)
110
response = self._client.call('BzrDir.open_branch', path)
111
if response[0] == 'ok':
112
if response[1] == '':
113
# branch at this location.
116
# a branch reference, use the existing BranchReference logic.
118
elif response == ('nobranch',):
119
raise errors.NotBranchError(path=self.root_transport.base)
121
raise errors.UnexpectedSmartServerResponse(response)
123
def open_branch(self, _unsupported=False):
124
assert _unsupported == False, 'unsupported flag support not implemented yet.'
125
reference_url = self.get_branch_reference()
126
if reference_url is None:
127
# branch at this location.
128
return RemoteBranch(self, self.find_repository())
130
# a branch reference, use the existing BranchReference logic.
131
format = BranchReferenceFormat()
132
return format.open(self, _found=True, location=reference_url)
134
def open_repository(self):
135
path = self._path_for_remote_call(self._client)
136
response = self._client.call('BzrDir.find_repository', path)
137
assert response[0] in ('ok', 'norepository'), \
138
'unexpected response code %s' % (response,)
139
if response[0] == 'norepository':
140
raise errors.NoRepositoryPresent(self)
141
assert len(response) == 4, 'incorrect response length %s' % (response,)
142
if response[1] == '':
143
format = RemoteRepositoryFormat()
144
format.rich_root_data = (response[2] == 'yes')
145
format.supports_tree_reference = (response[3] == 'yes')
146
return RemoteRepository(self, format)
148
raise errors.NoRepositoryPresent(self)
150
def open_workingtree(self, recommend_upgrade=True):
152
if self._real_bzrdir.has_workingtree():
153
raise errors.NotLocalUrl(self.root_transport)
155
raise errors.NoWorkingTree(self.root_transport.base)
157
def _path_for_remote_call(self, client):
158
"""Return the path to be used for this bzrdir in a remote call."""
159
return client.remote_path_from_transport(self.root_transport)
161
def get_branch_transport(self, branch_format):
163
return self._real_bzrdir.get_branch_transport(branch_format)
165
def get_repository_transport(self, repository_format):
167
return self._real_bzrdir.get_repository_transport(repository_format)
169
def get_workingtree_transport(self, workingtree_format):
171
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
173
def can_convert_format(self):
174
"""Upgrading of remote bzrdirs is not supported yet."""
177
def needs_format_conversion(self, format=None):
178
"""Upgrading of remote bzrdirs is not supported yet."""
181
def clone(self, url, revision_id=None, force_new_repo=False):
183
return self._real_bzrdir.clone(url, revision_id=revision_id,
184
force_new_repo=force_new_repo)
187
class RemoteRepositoryFormat(repository.RepositoryFormat):
188
"""Format for repositories accessed over a _SmartClient.
190
Instances of this repository are represented by RemoteRepository
193
The RemoteRepositoryFormat is parameterised during construction
194
to reflect the capabilities of the real, remote format. Specifically
195
the attributes rich_root_data and supports_tree_reference are set
196
on a per instance basis, and are not set (and should not be) at
200
_matchingbzrdir = RemoteBzrDirFormat
202
def initialize(self, a_bzrdir, shared=False):
203
assert isinstance(a_bzrdir, RemoteBzrDir), \
204
'%r is not a RemoteBzrDir' % (a_bzrdir,)
205
return a_bzrdir.create_repository(shared=shared)
207
def open(self, a_bzrdir):
208
assert isinstance(a_bzrdir, RemoteBzrDir)
209
return a_bzrdir.open_repository()
211
def get_format_description(self):
212
return 'bzr remote repository'
214
def __eq__(self, other):
215
return self.__class__ == other.__class__
217
def check_conversion_target(self, target_format):
218
if self.rich_root_data and not target_format.rich_root_data:
219
raise errors.BadConversionTarget(
220
'Does not support rich root data.', target_format)
221
if (self.supports_tree_reference and
222
not getattr(target_format, 'supports_tree_reference', False)):
223
raise errors.BadConversionTarget(
224
'Does not support nested trees', target_format)
227
class RemoteRepository(object):
228
"""Repository accessed over rpc.
230
For the moment most operations are performed using local transport-backed
234
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
235
"""Create a RemoteRepository instance.
237
:param remote_bzrdir: The bzrdir hosting this repository.
238
:param format: The RemoteFormat object to use.
239
:param real_repository: If not None, a local implementation of the
240
repository logic for the repository, usually accessing the data
242
:param _client: Private testing parameter - override the smart client
243
to be used by the repository.
246
self._real_repository = real_repository
248
self._real_repository = None
249
self.bzrdir = remote_bzrdir
251
self._client = client._SmartClient(self.bzrdir._shared_medium)
253
self._client = _client
254
self._format = format
255
self._lock_mode = None
256
self._lock_token = None
258
self._leave_lock = False
260
self._reconcile_does_inventory_gc = True
262
def abort_write_group(self):
263
"""Complete a write group on the decorated repository.
265
Smart methods peform operations in a single step so this api
266
is not really applicable except as a compatibility thunk
267
for older plugins that don't use e.g. the CommitBuilder
271
return self._real_repository.abort_write_group()
273
def commit_write_group(self):
274
"""Complete a write group on the decorated repository.
276
Smart methods peform operations in a single step so this api
277
is not really applicable except as a compatibility thunk
278
for older plugins that don't use e.g. the CommitBuilder
282
return self._real_repository.commit_write_group()
284
def _ensure_real(self):
285
"""Ensure that there is a _real_repository set.
287
Used before calls to self._real_repository.
289
if not self._real_repository:
290
self.bzrdir._ensure_real()
291
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
292
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
294
def get_revision_graph(self, revision_id=None):
295
"""See Repository.get_revision_graph()."""
296
if revision_id is None:
298
elif revision_id == NULL_REVISION:
301
path = self.bzrdir._path_for_remote_call(self._client)
302
assert type(revision_id) is str
303
response = self._client.call_expecting_body(
304
'Repository.get_revision_graph', path, revision_id)
305
if response[0][0] not in ['ok', 'nosuchrevision']:
306
raise errors.UnexpectedSmartServerResponse(response[0])
307
if response[0][0] == 'ok':
308
coded = response[1].read_body_bytes()
310
# no revisions in this repository!
312
lines = coded.split('\n')
315
d = tuple(line.split())
316
revision_graph[d[0]] = d[1:]
318
return revision_graph
320
response_body = response[1].read_body_bytes()
321
assert response_body == ''
322
raise NoSuchRevision(self, revision_id)
324
def has_revision(self, revision_id):
325
"""See Repository.has_revision()."""
326
if revision_id is None:
327
# The null revision is always present.
329
path = self.bzrdir._path_for_remote_call(self._client)
330
response = self._client.call('Repository.has_revision', path, revision_id)
331
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
332
return response[0] == 'yes'
334
def has_same_location(self, other):
335
return (self.__class__ == other.__class__ and
336
self.bzrdir.transport.base == other.bzrdir.transport.base)
338
def get_graph(self, other_repository=None):
339
"""Return the graph for this repository format"""
340
return self._real_repository.get_graph(other_repository)
342
def gather_stats(self, revid=None, committers=None):
343
"""See Repository.gather_stats()."""
344
path = self.bzrdir._path_for_remote_call(self._client)
345
if revid in (None, NULL_REVISION):
349
if committers is None or not committers:
350
fmt_committers = 'no'
352
fmt_committers = 'yes'
353
response = self._client.call_expecting_body(
354
'Repository.gather_stats', path, fmt_revid, fmt_committers)
355
assert response[0][0] == 'ok', \
356
'unexpected response code %s' % (response[0],)
358
body = response[1].read_body_bytes()
360
for line in body.split('\n'):
363
key, val_text = line.split(':')
364
if key in ('revisions', 'size', 'committers'):
365
result[key] = int(val_text)
366
elif key in ('firstrev', 'latestrev'):
367
values = val_text.split(' ')[1:]
368
result[key] = (float(values[0]), long(values[1]))
372
def get_physical_lock_status(self):
373
"""See Repository.get_physical_lock_status()."""
376
def is_in_write_group(self):
377
"""Return True if there is an open write group.
379
write groups are only applicable locally for the smart server..
381
if self._real_repository:
382
return self._real_repository.is_in_write_group()
385
return self._lock_count >= 1
388
"""See Repository.is_shared()."""
389
path = self.bzrdir._path_for_remote_call(self._client)
390
response = self._client.call('Repository.is_shared', path)
391
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
392
return response[0] == 'yes'
395
# wrong eventually - want a local lock cache context
396
if not self._lock_mode:
397
self._lock_mode = 'r'
399
if self._real_repository is not None:
400
self._real_repository.lock_read()
402
self._lock_count += 1
404
def _remote_lock_write(self, token):
405
path = self.bzrdir._path_for_remote_call(self._client)
408
response = self._client.call('Repository.lock_write', path, token)
409
if response[0] == 'ok':
412
elif response[0] == 'LockContention':
413
raise errors.LockContention('(remote lock)')
414
elif response[0] == 'UnlockableTransport':
415
raise errors.UnlockableTransport(self.bzrdir.root_transport)
417
raise errors.UnexpectedSmartServerResponse(response)
419
def lock_write(self, token=None):
420
if not self._lock_mode:
421
self._lock_token = self._remote_lock_write(token)
422
assert self._lock_token, 'Remote server did not return a token!'
423
if self._real_repository is not None:
424
self._real_repository.lock_write(token=self._lock_token)
425
if token is not None:
426
self._leave_lock = True
428
self._leave_lock = False
429
self._lock_mode = 'w'
431
elif self._lock_mode == 'r':
432
raise errors.ReadOnlyError(self)
434
self._lock_count += 1
435
return self._lock_token
437
def leave_lock_in_place(self):
438
self._leave_lock = True
440
def dont_leave_lock_in_place(self):
441
self._leave_lock = False
443
def _set_real_repository(self, repository):
444
"""Set the _real_repository for this repository.
446
:param repository: The repository to fallback to for non-hpss
447
implemented operations.
449
assert not isinstance(repository, RemoteRepository)
450
self._real_repository = repository
451
if self._lock_mode == 'w':
452
# if we are already locked, the real repository must be able to
453
# acquire the lock with our token.
454
self._real_repository.lock_write(self._lock_token)
455
elif self._lock_mode == 'r':
456
self._real_repository.lock_read()
458
def start_write_group(self):
459
"""Start a write group on the decorated repository.
461
Smart methods peform operations in a single step so this api
462
is not really applicable except as a compatibility thunk
463
for older plugins that don't use e.g. the CommitBuilder
467
return self._real_repository.start_write_group()
469
def _unlock(self, token):
470
path = self.bzrdir._path_for_remote_call(self._client)
471
response = self._client.call('Repository.unlock', path, token)
472
if response == ('ok',):
474
elif response[0] == 'TokenMismatch':
475
raise errors.TokenMismatch(token, '(remote token)')
477
raise errors.UnexpectedSmartServerResponse(response)
480
if self._lock_count == 1 and self._lock_mode == 'w':
481
# don't unlock if inside a write group.
482
if self.is_in_write_group():
483
raise errors.BzrError(
484
'Must end write groups before releasing write locks.')
485
self._lock_count -= 1
486
if not self._lock_count:
487
mode = self._lock_mode
488
self._lock_mode = None
489
if self._real_repository is not None:
490
self._real_repository.unlock()
492
# Only write-locked repositories need to make a remote method
493
# call to perfom the unlock.
495
assert self._lock_token, 'Locked, but no token!'
496
token = self._lock_token
497
self._lock_token = None
498
if not self._leave_lock:
501
def break_lock(self):
502
# should hand off to the network
504
return self._real_repository.break_lock()
506
def _get_tarball(self, compression):
507
"""Return a TemporaryFile containing a repository tarball"""
509
path = self.bzrdir._path_for_remote_call(self._client)
510
response, protocol = self._client.call_expecting_body(
511
'Repository.tarball', path, compression)
512
assert response[0] in ('ok', 'failure'), \
513
'unexpected response code %s' % (response,)
514
if response[0] == 'ok':
515
# Extract the tarball and return it
516
t = tempfile.NamedTemporaryFile()
517
# TODO: rpc layer should read directly into it...
518
t.write(protocol.read_body_bytes())
522
raise errors.SmartServerError(error_code=response)
524
def sprout(self, to_bzrdir, revision_id=None):
525
# TODO: Option to control what format is created?
526
to_repo = to_bzrdir.create_repository()
527
self._copy_repository_tarball(to_repo, revision_id)
530
### These methods are just thin shims to the VFS object for now.
532
def revision_tree(self, revision_id):
534
return self._real_repository.revision_tree(revision_id)
536
def get_serializer_format(self):
538
return self._real_repository.get_serializer_format()
540
def get_commit_builder(self, branch, parents, config, timestamp=None,
541
timezone=None, committer=None, revprops=None,
543
# FIXME: It ought to be possible to call this without immediately
544
# triggering _ensure_real. For now it's the easiest thing to do.
546
builder = self._real_repository.get_commit_builder(branch, parents,
547
config, timestamp=timestamp, timezone=timezone,
548
committer=committer, revprops=revprops, revision_id=revision_id)
549
# Make the builder use this RemoteRepository rather than the real one.
550
builder.repository = self
554
def add_inventory(self, revid, inv, parents):
556
return self._real_repository.add_inventory(revid, inv, parents)
559
def add_revision(self, rev_id, rev, inv=None, config=None):
561
return self._real_repository.add_revision(
562
rev_id, rev, inv=inv, config=config)
565
def get_inventory(self, revision_id):
567
return self._real_repository.get_inventory(revision_id)
570
def get_revision(self, revision_id):
572
return self._real_repository.get_revision(revision_id)
575
def weave_store(self):
577
return self._real_repository.weave_store
579
def get_transaction(self):
581
return self._real_repository.get_transaction()
584
def clone(self, a_bzrdir, revision_id=None):
586
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
588
def make_working_trees(self):
589
"""RemoteRepositories never create working trees by default."""
592
def fetch(self, source, revision_id=None, pb=None):
594
return self._real_repository.fetch(
595
source, revision_id=revision_id, pb=pb)
597
def create_bundle(self, target, base, fileobj, format=None):
599
self._real_repository.create_bundle(target, base, fileobj, format)
602
def control_weaves(self):
604
return self._real_repository.control_weaves
607
def get_ancestry(self, revision_id, topo_sorted=True):
609
return self._real_repository.get_ancestry(revision_id, topo_sorted)
612
def get_inventory_weave(self):
614
return self._real_repository.get_inventory_weave()
616
def fileids_altered_by_revision_ids(self, revision_ids):
618
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
620
def iter_files_bytes(self, desired_files):
621
"""See Repository.iter_file_bytes.
624
return self._real_repository.iter_files_bytes(desired_files)
627
def get_signature_text(self, revision_id):
629
return self._real_repository.get_signature_text(revision_id)
632
def get_revision_graph_with_ghosts(self, revision_ids=None):
634
return self._real_repository.get_revision_graph_with_ghosts(
635
revision_ids=revision_ids)
638
def get_inventory_xml(self, revision_id):
640
return self._real_repository.get_inventory_xml(revision_id)
642
def deserialise_inventory(self, revision_id, xml):
644
return self._real_repository.deserialise_inventory(revision_id, xml)
646
def reconcile(self, other=None, thorough=False):
648
return self._real_repository.reconcile(other=other, thorough=thorough)
650
def all_revision_ids(self):
652
return self._real_repository.all_revision_ids()
655
def get_deltas_for_revisions(self, revisions):
657
return self._real_repository.get_deltas_for_revisions(revisions)
660
def get_revision_delta(self, revision_id):
662
return self._real_repository.get_revision_delta(revision_id)
665
def revision_trees(self, revision_ids):
667
return self._real_repository.revision_trees(revision_ids)
670
def get_revision_reconcile(self, revision_id):
672
return self._real_repository.get_revision_reconcile(revision_id)
675
def check(self, revision_ids):
677
return self._real_repository.check(revision_ids)
679
def copy_content_into(self, destination, revision_id=None):
681
return self._real_repository.copy_content_into(
682
destination, revision_id=revision_id)
684
def _copy_repository_tarball(self, destination, revision_id=None):
685
# get a tarball of the remote repository, and copy from that into the
687
from bzrlib import osutils
690
from StringIO import StringIO
691
# TODO: Maybe a progress bar while streaming the tarball?
692
note("Copying repository content as tarball...")
693
tar_file = self._get_tarball('bz2')
695
tar = tarfile.open('repository', fileobj=tar_file,
697
tmpdir = tempfile.mkdtemp()
699
_extract_tar(tar, tmpdir)
700
tmp_bzrdir = BzrDir.open(tmpdir)
701
tmp_repo = tmp_bzrdir.open_repository()
702
tmp_repo.copy_content_into(destination, revision_id)
704
osutils.rmtree(tmpdir)
707
# TODO: if the server doesn't support this operation, maybe do it the
708
# slow way using the _real_repository?
710
# TODO: Suggestion from john: using external tar is much faster than
711
# python's tarfile library, but it may not work on windows.
715
"""Compress the data within the repository.
717
This is not currently implemented within the smart server.
720
return self._real_repository.pack()
722
def set_make_working_trees(self, new_value):
723
raise NotImplementedError(self.set_make_working_trees)
726
def sign_revision(self, revision_id, gpg_strategy):
728
return self._real_repository.sign_revision(revision_id, gpg_strategy)
731
def get_revisions(self, revision_ids):
733
return self._real_repository.get_revisions(revision_ids)
735
def supports_rich_root(self):
737
return self._real_repository.supports_rich_root()
739
def iter_reverse_revision_history(self, revision_id):
741
return self._real_repository.iter_reverse_revision_history(revision_id)
744
def _serializer(self):
746
return self._real_repository._serializer
748
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
750
return self._real_repository.store_revision_signature(
751
gpg_strategy, plaintext, revision_id)
753
def has_signature_for_revision_id(self, revision_id):
755
return self._real_repository.has_signature_for_revision_id(revision_id)
758
class RemoteBranchLockableFiles(LockableFiles):
759
"""A 'LockableFiles' implementation that talks to a smart server.
761
This is not a public interface class.
764
def __init__(self, bzrdir, _client):
766
self._client = _client
767
self._need_find_modes = True
768
LockableFiles.__init__(
769
self, bzrdir.get_branch_transport(None),
770
'lock', lockdir.LockDir)
772
def _find_modes(self):
773
# RemoteBranches don't let the client set the mode of control files.
774
self._dir_mode = None
775
self._file_mode = None
778
"""'get' a remote path as per the LockableFiles interface.
780
:param path: the file to 'get'. If this is 'branch.conf', we do not
781
just retrieve a file, instead we ask the smart server to generate
782
a configuration for us - which is retrieved as an INI file.
784
if path == 'branch.conf':
785
path = self.bzrdir._path_for_remote_call(self._client)
786
response = self._client.call_expecting_body(
787
'Branch.get_config_file', path)
788
assert response[0][0] == 'ok', \
789
'unexpected response code %s' % (response[0],)
790
return StringIO(response[1].read_body_bytes())
793
return LockableFiles.get(self, path)
796
class RemoteBranchFormat(branch.BranchFormat):
798
def __eq__(self, other):
799
return (isinstance(other, RemoteBranchFormat) and
800
self.__dict__ == other.__dict__)
802
def get_format_description(self):
803
return 'Remote BZR Branch'
805
def get_format_string(self):
806
return 'Remote BZR Branch'
808
def open(self, a_bzrdir):
809
assert isinstance(a_bzrdir, RemoteBzrDir)
810
return a_bzrdir.open_branch()
812
def initialize(self, a_bzrdir):
813
assert isinstance(a_bzrdir, RemoteBzrDir)
814
return a_bzrdir.create_branch()
816
def supports_tags(self):
817
# Remote branches might support tags, but we won't know until we
818
# access the real remote branch.
822
class RemoteBranch(branch.Branch):
823
"""Branch stored on a server accessed by HPSS RPC.
825
At the moment most operations are mapped down to simple file operations.
828
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
830
"""Create a RemoteBranch instance.
832
:param real_branch: An optional local implementation of the branch
833
format, usually accessing the data via the VFS.
834
:param _client: Private parameter for testing.
836
# We intentionally don't call the parent class's __init__, because it
837
# will try to assign to self.tags, which is a property in this subclass.
838
# And the parent's __init__ doesn't do much anyway.
839
self._revision_history_cache = None
840
self.bzrdir = remote_bzrdir
841
if _client is not None:
842
self._client = _client
844
self._client = client._SmartClient(self.bzrdir._shared_medium)
845
self.repository = remote_repository
846
if real_branch is not None:
847
self._real_branch = real_branch
848
# Give the remote repository the matching real repo.
849
real_repo = self._real_branch.repository
850
if isinstance(real_repo, RemoteRepository):
851
real_repo._ensure_real()
852
real_repo = real_repo._real_repository
853
self.repository._set_real_repository(real_repo)
854
# Give the branch the remote repository to let fast-pathing happen.
855
self._real_branch.repository = self.repository
857
self._real_branch = None
858
# Fill out expected attributes of branch for bzrlib api users.
859
self._format = RemoteBranchFormat()
860
self.base = self.bzrdir.root_transport.base
861
self._control_files = None
862
self._lock_mode = None
863
self._lock_token = None
865
self._leave_lock = False
868
return "%s(%s)" % (self.__class__.__name__, self.base)
872
def _ensure_real(self):
873
"""Ensure that there is a _real_branch set.
875
Used before calls to self._real_branch.
877
if not self._real_branch:
878
assert vfs.vfs_enabled()
879
self.bzrdir._ensure_real()
880
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
881
# Give the remote repository the matching real repo.
882
real_repo = self._real_branch.repository
883
if isinstance(real_repo, RemoteRepository):
884
real_repo._ensure_real()
885
real_repo = real_repo._real_repository
886
self.repository._set_real_repository(real_repo)
887
# Give the branch the remote repository to let fast-pathing happen.
888
self._real_branch.repository = self.repository
889
# XXX: deal with _lock_mode == 'w'
890
if self._lock_mode == 'r':
891
self._real_branch.lock_read()
894
def control_files(self):
895
# Defer actually creating RemoteBranchLockableFiles until its needed,
896
# because it triggers an _ensure_real that we otherwise might not need.
897
if self._control_files is None:
898
self._control_files = RemoteBranchLockableFiles(
899
self.bzrdir, self._client)
900
return self._control_files
902
def _get_checkout_format(self):
904
return self._real_branch._get_checkout_format()
906
def get_physical_lock_status(self):
907
"""See Branch.get_physical_lock_status()."""
908
# should be an API call to the server, as branches must be lockable.
910
return self._real_branch.get_physical_lock_status()
913
if not self._lock_mode:
914
self._lock_mode = 'r'
916
if self._real_branch is not None:
917
self._real_branch.lock_read()
919
self._lock_count += 1
921
def _remote_lock_write(self, token):
923
branch_token = repo_token = ''
926
repo_token = self.repository.lock_write()
927
self.repository.unlock()
928
path = self.bzrdir._path_for_remote_call(self._client)
929
response = self._client.call('Branch.lock_write', path, branch_token,
931
if response[0] == 'ok':
932
ok, branch_token, repo_token = response
933
return branch_token, repo_token
934
elif response[0] == 'LockContention':
935
raise errors.LockContention('(remote lock)')
936
elif response[0] == 'TokenMismatch':
937
raise errors.TokenMismatch(token, '(remote token)')
938
elif response[0] == 'UnlockableTransport':
939
raise errors.UnlockableTransport(self.bzrdir.root_transport)
940
elif response[0] == 'ReadOnlyError':
941
raise errors.ReadOnlyError(self)
943
raise errors.UnexpectedSmartServerResponse(response)
945
def lock_write(self, token=None):
946
if not self._lock_mode:
947
remote_tokens = self._remote_lock_write(token)
948
self._lock_token, self._repo_lock_token = remote_tokens
949
assert self._lock_token, 'Remote server did not return a token!'
950
# TODO: We really, really, really don't want to call _ensure_real
951
# here, but it's the easiest way to ensure coherency between the
952
# state of the RemoteBranch and RemoteRepository objects and the
953
# physical locks. If we don't materialise the real objects here,
954
# then getting everything in the right state later is complex, so
955
# for now we just do it the lazy way.
956
# -- Andrew Bennetts, 2007-02-22.
958
if self._real_branch is not None:
959
self._real_branch.repository.lock_write(
960
token=self._repo_lock_token)
962
self._real_branch.lock_write(token=self._lock_token)
964
self._real_branch.repository.unlock()
965
if token is not None:
966
self._leave_lock = True
968
# XXX: this case seems to be unreachable; token cannot be None.
969
self._leave_lock = False
970
self._lock_mode = 'w'
972
elif self._lock_mode == 'r':
973
raise errors.ReadOnlyTransaction
975
if token is not None:
976
# A token was given to lock_write, and we're relocking, so check
977
# that the given token actually matches the one we already have.
978
if token != self._lock_token:
979
raise errors.TokenMismatch(token, self._lock_token)
980
self._lock_count += 1
981
return self._lock_token
983
def _unlock(self, branch_token, repo_token):
984
path = self.bzrdir._path_for_remote_call(self._client)
985
response = self._client.call('Branch.unlock', path, branch_token,
987
if response == ('ok',):
989
elif response[0] == 'TokenMismatch':
990
raise errors.TokenMismatch(
991
str((branch_token, repo_token)), '(remote tokens)')
993
raise errors.UnexpectedSmartServerResponse(response)
996
self._lock_count -= 1
997
if not self._lock_count:
998
self._clear_cached_state()
999
mode = self._lock_mode
1000
self._lock_mode = None
1001
if self._real_branch is not None:
1002
if not self._leave_lock:
1003
# If this RemoteBranch will remove the physical lock for the
1004
# repository, make sure the _real_branch doesn't do it
1005
# first. (Because the _real_branch's repository is set to
1006
# be the RemoteRepository.)
1007
self._real_branch.repository.leave_lock_in_place()
1008
self._real_branch.unlock()
1010
# Only write-locked branched need to make a remote method call
1011
# to perfom the unlock.
1013
assert self._lock_token, 'Locked, but no token!'
1014
branch_token = self._lock_token
1015
repo_token = self._repo_lock_token
1016
self._lock_token = None
1017
self._repo_lock_token = None
1018
if not self._leave_lock:
1019
self._unlock(branch_token, repo_token)
1021
def break_lock(self):
1023
return self._real_branch.break_lock()
1025
def leave_lock_in_place(self):
1026
self._leave_lock = True
1028
def dont_leave_lock_in_place(self):
1029
self._leave_lock = False
1031
def last_revision_info(self):
1032
"""See Branch.last_revision_info()."""
1033
path = self.bzrdir._path_for_remote_call(self._client)
1034
response = self._client.call('Branch.last_revision_info', path)
1035
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1036
revno = int(response[1])
1037
last_revision = response[2]
1038
return (revno, last_revision)
1040
def _gen_revision_history(self):
1041
"""See Branch._gen_revision_history()."""
1042
path = self.bzrdir._path_for_remote_call(self._client)
1043
response = self._client.call_expecting_body(
1044
'Branch.revision_history', path)
1045
assert response[0][0] == 'ok', ('unexpected response code %s'
1047
result = response[1].read_body_bytes().split('\x00')
1053
def set_revision_history(self, rev_history):
1054
# Send just the tip revision of the history; the server will generate
1055
# the full history from that. If the revision doesn't exist in this
1056
# branch, NoSuchRevision will be raised.
1057
path = self.bzrdir._path_for_remote_call(self._client)
1058
if rev_history == []:
1061
rev_id = rev_history[-1]
1062
self._clear_cached_state()
1063
response = self._client.call('Branch.set_last_revision',
1064
path, self._lock_token, self._repo_lock_token, rev_id)
1065
if response[0] == 'NoSuchRevision':
1066
raise NoSuchRevision(self, rev_id)
1068
assert response == ('ok',), (
1069
'unexpected response code %r' % (response,))
1070
self._cache_revision_history(rev_history)
1072
def get_parent(self):
1074
return self._real_branch.get_parent()
1076
def set_parent(self, url):
1078
return self._real_branch.set_parent(url)
1080
def get_config(self):
1081
return RemoteBranchConfig(self)
1083
def sprout(self, to_bzrdir, revision_id=None):
1084
# Like Branch.sprout, except that it sprouts a branch in the default
1085
# format, because RemoteBranches can't be created at arbitrary URLs.
1086
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1087
# to_bzrdir.create_branch...
1088
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1089
self.copy_content_into(result, revision_id=revision_id)
1090
result.set_parent(self.bzrdir.root_transport.base)
1094
def pull(self, source, overwrite=False, stop_revision=None,
1096
# FIXME: This asks the real branch to run the hooks, which means
1097
# they're called with the wrong target branch parameter.
1098
# The test suite specifically allows this at present but it should be
1099
# fixed. It should get a _override_hook_target branch,
1100
# as push does. -- mbp 20070405
1102
self._real_branch.pull(
1103
source, overwrite=overwrite, stop_revision=stop_revision,
1107
def push(self, target, overwrite=False, stop_revision=None):
1109
return self._real_branch.push(
1110
target, overwrite=overwrite, stop_revision=stop_revision,
1111
_override_hook_source_branch=self)
1113
def is_locked(self):
1114
return self._lock_count >= 1
1116
def set_last_revision_info(self, revno, revision_id):
1118
self._clear_cached_state()
1119
return self._real_branch.set_last_revision_info(revno, revision_id)
1121
def generate_revision_history(self, revision_id, last_rev=None,
1124
return self._real_branch.generate_revision_history(
1125
revision_id, last_rev=last_rev, other_branch=other_branch)
1130
return self._real_branch.tags
1132
def set_push_location(self, location):
1134
return self._real_branch.set_push_location(location)
1136
def update_revisions(self, other, stop_revision=None):
1138
return self._real_branch.update_revisions(
1139
other, stop_revision=stop_revision)
1142
class RemoteBranchConfig(BranchConfig):
1145
self.branch._ensure_real()
1146
return self.branch._real_branch.get_config().username()
1148
def _get_branch_data_config(self):
1149
self.branch._ensure_real()
1150
if self._branch_data_config is None:
1151
self._branch_data_config = TreeConfig(self.branch._real_branch)
1152
return self._branch_data_config
1155
def _extract_tar(tar, to_dir):
1156
"""Extract all the contents of a tarfile object.
1158
A replacement for extractall, which is not present in python2.4
1161
tar.extract(tarinfo, to_dir)