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)
418
raise errors.UnexpectedSmartServerResponse(response)
420
def lock_write(self, token=None):
421
if not self._lock_mode:
422
self._lock_token = self._remote_lock_write(token)
423
assert self._lock_token, 'Remote server did not return a token!'
424
if self._real_repository is not None:
425
self._real_repository.lock_write(token=self._lock_token)
426
if token is not None:
427
self._leave_lock = True
429
self._leave_lock = False
430
self._lock_mode = 'w'
432
elif self._lock_mode == 'r':
433
raise errors.ReadOnlyError(self)
435
self._lock_count += 1
436
return self._lock_token
438
def leave_lock_in_place(self):
439
self._leave_lock = True
441
def dont_leave_lock_in_place(self):
442
self._leave_lock = False
444
def _set_real_repository(self, repository):
445
"""Set the _real_repository for this repository.
447
:param repository: The repository to fallback to for non-hpss
448
implemented operations.
450
assert not isinstance(repository, RemoteRepository)
451
self._real_repository = repository
452
if self._lock_mode == 'w':
453
# if we are already locked, the real repository must be able to
454
# acquire the lock with our token.
455
self._real_repository.lock_write(self._lock_token)
456
elif self._lock_mode == 'r':
457
self._real_repository.lock_read()
459
def start_write_group(self):
460
"""Start a write group on the decorated repository.
462
Smart methods peform operations in a single step so this api
463
is not really applicable except as a compatibility thunk
464
for older plugins that don't use e.g. the CommitBuilder
468
return self._real_repository.start_write_group()
470
def _unlock(self, token):
471
path = self.bzrdir._path_for_remote_call(self._client)
472
response = self._client.call('Repository.unlock', path, token)
473
if response == ('ok',):
475
elif response[0] == 'TokenMismatch':
476
raise errors.TokenMismatch(token, '(remote token)')
478
raise errors.UnexpectedSmartServerResponse(response)
481
if self._lock_count == 1 and self._lock_mode == 'w':
482
# don't unlock if inside a write group.
483
if self.is_in_write_group():
484
raise errors.BzrError(
485
'Must end write groups before releasing write locks.')
486
self._lock_count -= 1
487
if not self._lock_count:
488
mode = self._lock_mode
489
self._lock_mode = None
490
if self._real_repository is not None:
491
self._real_repository.unlock()
493
# Only write-locked repositories need to make a remote method
494
# call to perfom the unlock.
496
assert self._lock_token, 'Locked, but no token!'
497
token = self._lock_token
498
self._lock_token = None
499
if not self._leave_lock:
502
def break_lock(self):
503
# should hand off to the network
505
return self._real_repository.break_lock()
507
def _get_tarball(self, compression):
508
"""Return a TemporaryFile containing a repository tarball"""
510
path = self.bzrdir._path_for_remote_call(self._client)
511
response, protocol = self._client.call_expecting_body(
512
'Repository.tarball', path, compression)
513
assert response[0] in ('ok', 'failure'), \
514
'unexpected response code %s' % (response,)
515
if response[0] == 'ok':
516
# Extract the tarball and return it
517
t = tempfile.NamedTemporaryFile()
518
# TODO: rpc layer should read directly into it...
519
t.write(protocol.read_body_bytes())
523
raise errors.SmartServerError(error_code=response)
525
def sprout(self, to_bzrdir, revision_id=None):
526
# TODO: Option to control what format is created?
527
to_repo = to_bzrdir.create_repository()
528
self._copy_repository_tarball(to_repo, revision_id)
531
### These methods are just thin shims to the VFS object for now.
533
def revision_tree(self, revision_id):
535
return self._real_repository.revision_tree(revision_id)
537
def get_serializer_format(self):
539
return self._real_repository.get_serializer_format()
541
def get_commit_builder(self, branch, parents, config, timestamp=None,
542
timezone=None, committer=None, revprops=None,
544
# FIXME: It ought to be possible to call this without immediately
545
# triggering _ensure_real. For now it's the easiest thing to do.
547
builder = self._real_repository.get_commit_builder(branch, parents,
548
config, timestamp=timestamp, timezone=timezone,
549
committer=committer, revprops=revprops, revision_id=revision_id)
550
# Make the builder use this RemoteRepository rather than the real one.
551
builder.repository = self
555
def add_inventory(self, revid, inv, parents):
557
return self._real_repository.add_inventory(revid, inv, parents)
560
def add_revision(self, rev_id, rev, inv=None, config=None):
562
return self._real_repository.add_revision(
563
rev_id, rev, inv=inv, config=config)
566
def get_inventory(self, revision_id):
568
return self._real_repository.get_inventory(revision_id)
571
def get_revision(self, revision_id):
573
return self._real_repository.get_revision(revision_id)
576
def weave_store(self):
578
return self._real_repository.weave_store
580
def get_transaction(self):
582
return self._real_repository.get_transaction()
585
def clone(self, a_bzrdir, revision_id=None):
587
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
589
def make_working_trees(self):
590
"""RemoteRepositories never create working trees by default."""
593
def fetch(self, source, revision_id=None, pb=None):
595
return self._real_repository.fetch(
596
source, revision_id=revision_id, pb=pb)
598
def create_bundle(self, target, base, fileobj, format=None):
600
self._real_repository.create_bundle(target, base, fileobj, format)
603
def control_weaves(self):
605
return self._real_repository.control_weaves
608
def get_ancestry(self, revision_id, topo_sorted=True):
610
return self._real_repository.get_ancestry(revision_id, topo_sorted)
613
def get_inventory_weave(self):
615
return self._real_repository.get_inventory_weave()
617
def fileids_altered_by_revision_ids(self, revision_ids):
619
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
621
def iter_files_bytes(self, desired_files):
622
"""See Repository.iter_file_bytes.
625
return self._real_repository.iter_files_bytes(desired_files)
628
def get_signature_text(self, revision_id):
630
return self._real_repository.get_signature_text(revision_id)
633
def get_revision_graph_with_ghosts(self, revision_ids=None):
635
return self._real_repository.get_revision_graph_with_ghosts(
636
revision_ids=revision_ids)
639
def get_inventory_xml(self, revision_id):
641
return self._real_repository.get_inventory_xml(revision_id)
643
def deserialise_inventory(self, revision_id, xml):
645
return self._real_repository.deserialise_inventory(revision_id, xml)
647
def reconcile(self, other=None, thorough=False):
649
return self._real_repository.reconcile(other=other, thorough=thorough)
651
def all_revision_ids(self):
653
return self._real_repository.all_revision_ids()
656
def get_deltas_for_revisions(self, revisions):
658
return self._real_repository.get_deltas_for_revisions(revisions)
661
def get_revision_delta(self, revision_id):
663
return self._real_repository.get_revision_delta(revision_id)
666
def revision_trees(self, revision_ids):
668
return self._real_repository.revision_trees(revision_ids)
671
def get_revision_reconcile(self, revision_id):
673
return self._real_repository.get_revision_reconcile(revision_id)
676
def check(self, revision_ids):
678
return self._real_repository.check(revision_ids)
680
def copy_content_into(self, destination, revision_id=None):
682
return self._real_repository.copy_content_into(
683
destination, revision_id=revision_id)
685
def _copy_repository_tarball(self, destination, revision_id=None):
686
# get a tarball of the remote repository, and copy from that into the
688
from bzrlib import osutils
691
from StringIO import StringIO
692
# TODO: Maybe a progress bar while streaming the tarball?
693
note("Copying repository content as tarball...")
694
tar_file = self._get_tarball('bz2')
696
tar = tarfile.open('repository', fileobj=tar_file,
698
tmpdir = tempfile.mkdtemp()
700
_extract_tar(tar, tmpdir)
701
tmp_bzrdir = BzrDir.open(tmpdir)
702
tmp_repo = tmp_bzrdir.open_repository()
703
tmp_repo.copy_content_into(destination, revision_id)
705
osutils.rmtree(tmpdir)
708
# TODO: if the server doesn't support this operation, maybe do it the
709
# slow way using the _real_repository?
711
# TODO: Suggestion from john: using external tar is much faster than
712
# python's tarfile library, but it may not work on windows.
716
"""Compress the data within the repository.
718
This is not currently implemented within the smart server.
721
return self._real_repository.pack()
723
def set_make_working_trees(self, new_value):
724
raise NotImplementedError(self.set_make_working_trees)
727
def sign_revision(self, revision_id, gpg_strategy):
729
return self._real_repository.sign_revision(revision_id, gpg_strategy)
732
def get_revisions(self, revision_ids):
734
return self._real_repository.get_revisions(revision_ids)
736
def supports_rich_root(self):
738
return self._real_repository.supports_rich_root()
740
def iter_reverse_revision_history(self, revision_id):
742
return self._real_repository.iter_reverse_revision_history(revision_id)
745
def _serializer(self):
747
return self._real_repository._serializer
749
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
751
return self._real_repository.store_revision_signature(
752
gpg_strategy, plaintext, revision_id)
754
def has_signature_for_revision_id(self, revision_id):
756
return self._real_repository.has_signature_for_revision_id(revision_id)
759
class RemoteBranchLockableFiles(LockableFiles):
760
"""A 'LockableFiles' implementation that talks to a smart server.
762
This is not a public interface class.
765
def __init__(self, bzrdir, _client):
767
self._client = _client
768
self._need_find_modes = True
769
LockableFiles.__init__(
770
self, bzrdir.get_branch_transport(None),
771
'lock', lockdir.LockDir)
773
def _find_modes(self):
774
# RemoteBranches don't let the client set the mode of control files.
775
self._dir_mode = None
776
self._file_mode = None
779
"""'get' a remote path as per the LockableFiles interface.
781
:param path: the file to 'get'. If this is 'branch.conf', we do not
782
just retrieve a file, instead we ask the smart server to generate
783
a configuration for us - which is retrieved as an INI file.
785
if path == 'branch.conf':
786
path = self.bzrdir._path_for_remote_call(self._client)
787
response = self._client.call_expecting_body(
788
'Branch.get_config_file', path)
789
assert response[0][0] == 'ok', \
790
'unexpected response code %s' % (response[0],)
791
return StringIO(response[1].read_body_bytes())
794
return LockableFiles.get(self, path)
797
class RemoteBranchFormat(branch.BranchFormat):
799
def __eq__(self, other):
800
return (isinstance(other, RemoteBranchFormat) and
801
self.__dict__ == other.__dict__)
803
def get_format_description(self):
804
return 'Remote BZR Branch'
806
def get_format_string(self):
807
return 'Remote BZR Branch'
809
def open(self, a_bzrdir):
810
assert isinstance(a_bzrdir, RemoteBzrDir)
811
return a_bzrdir.open_branch()
813
def initialize(self, a_bzrdir):
814
assert isinstance(a_bzrdir, RemoteBzrDir)
815
return a_bzrdir.create_branch()
817
def supports_tags(self):
818
# Remote branches might support tags, but we won't know until we
819
# access the real remote branch.
823
class RemoteBranch(branch.Branch):
824
"""Branch stored on a server accessed by HPSS RPC.
826
At the moment most operations are mapped down to simple file operations.
829
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
831
"""Create a RemoteBranch instance.
833
:param real_branch: An optional local implementation of the branch
834
format, usually accessing the data via the VFS.
835
:param _client: Private parameter for testing.
837
# We intentionally don't call the parent class's __init__, because it
838
# will try to assign to self.tags, which is a property in this subclass.
839
# And the parent's __init__ doesn't do much anyway.
840
self._revision_history_cache = None
841
self.bzrdir = remote_bzrdir
842
if _client is not None:
843
self._client = _client
845
self._client = client._SmartClient(self.bzrdir._shared_medium)
846
self.repository = remote_repository
847
if real_branch is not None:
848
self._real_branch = real_branch
849
# Give the remote repository the matching real repo.
850
real_repo = self._real_branch.repository
851
if isinstance(real_repo, RemoteRepository):
852
real_repo._ensure_real()
853
real_repo = real_repo._real_repository
854
self.repository._set_real_repository(real_repo)
855
# Give the branch the remote repository to let fast-pathing happen.
856
self._real_branch.repository = self.repository
858
self._real_branch = None
859
# Fill out expected attributes of branch for bzrlib api users.
860
self._format = RemoteBranchFormat()
861
self.base = self.bzrdir.root_transport.base
862
self._control_files = None
863
self._lock_mode = None
864
self._lock_token = None
866
self._leave_lock = False
869
return "%s(%s)" % (self.__class__.__name__, self.base)
873
def _ensure_real(self):
874
"""Ensure that there is a _real_branch set.
876
Used before calls to self._real_branch.
878
if not self._real_branch:
879
assert vfs.vfs_enabled()
880
self.bzrdir._ensure_real()
881
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
882
# Give the remote repository the matching real repo.
883
real_repo = self._real_branch.repository
884
if isinstance(real_repo, RemoteRepository):
885
real_repo._ensure_real()
886
real_repo = real_repo._real_repository
887
self.repository._set_real_repository(real_repo)
888
# Give the branch the remote repository to let fast-pathing happen.
889
self._real_branch.repository = self.repository
890
# XXX: deal with _lock_mode == 'w'
891
if self._lock_mode == 'r':
892
self._real_branch.lock_read()
895
def control_files(self):
896
# Defer actually creating RemoteBranchLockableFiles until its needed,
897
# because it triggers an _ensure_real that we otherwise might not need.
898
if self._control_files is None:
899
self._control_files = RemoteBranchLockableFiles(
900
self.bzrdir, self._client)
901
return self._control_files
903
def _get_checkout_format(self):
905
return self._real_branch._get_checkout_format()
907
def get_physical_lock_status(self):
908
"""See Branch.get_physical_lock_status()."""
909
# should be an API call to the server, as branches must be lockable.
911
return self._real_branch.get_physical_lock_status()
914
if not self._lock_mode:
915
self._lock_mode = 'r'
917
if self._real_branch is not None:
918
self._real_branch.lock_read()
920
self._lock_count += 1
922
def _remote_lock_write(self, token):
924
branch_token = repo_token = ''
927
repo_token = self.repository.lock_write()
928
self.repository.unlock()
929
path = self.bzrdir._path_for_remote_call(self._client)
930
response = self._client.call('Branch.lock_write', path, branch_token,
932
if response[0] == 'ok':
933
ok, branch_token, repo_token = response
934
return branch_token, repo_token
935
elif response[0] == 'LockContention':
936
raise errors.LockContention('(remote lock)')
937
elif response[0] == 'TokenMismatch':
938
raise errors.TokenMismatch(token, '(remote token)')
939
elif response[0] == 'UnlockableTransport':
940
raise errors.UnlockableTransport(self.bzrdir.root_transport)
941
elif response[0] == 'ReadOnlyError':
942
raise errors.ReadOnlyError(self)
944
raise errors.UnexpectedSmartServerResponse(response)
946
def lock_write(self, token=None):
947
if not self._lock_mode:
948
remote_tokens = self._remote_lock_write(token)
949
self._lock_token, self._repo_lock_token = remote_tokens
950
assert self._lock_token, 'Remote server did not return a token!'
951
# TODO: We really, really, really don't want to call _ensure_real
952
# here, but it's the easiest way to ensure coherency between the
953
# state of the RemoteBranch and RemoteRepository objects and the
954
# physical locks. If we don't materialise the real objects here,
955
# then getting everything in the right state later is complex, so
956
# for now we just do it the lazy way.
957
# -- Andrew Bennetts, 2007-02-22.
959
if self._real_branch is not None:
960
self._real_branch.repository.lock_write(
961
token=self._repo_lock_token)
963
self._real_branch.lock_write(token=self._lock_token)
965
self._real_branch.repository.unlock()
966
if token is not None:
967
self._leave_lock = True
969
# XXX: this case seems to be unreachable; token cannot be None.
970
self._leave_lock = False
971
self._lock_mode = 'w'
973
elif self._lock_mode == 'r':
974
raise errors.ReadOnlyTransaction
976
if token is not None:
977
# A token was given to lock_write, and we're relocking, so check
978
# that the given token actually matches the one we already have.
979
if token != self._lock_token:
980
raise errors.TokenMismatch(token, self._lock_token)
981
self._lock_count += 1
982
return self._lock_token
984
def _unlock(self, branch_token, repo_token):
985
path = self.bzrdir._path_for_remote_call(self._client)
986
response = self._client.call('Branch.unlock', path, branch_token,
988
if response == ('ok',):
990
elif response[0] == 'TokenMismatch':
991
raise errors.TokenMismatch(
992
str((branch_token, repo_token)), '(remote tokens)')
994
raise errors.UnexpectedSmartServerResponse(response)
997
self._lock_count -= 1
998
if not self._lock_count:
999
self._clear_cached_state()
1000
mode = self._lock_mode
1001
self._lock_mode = None
1002
if self._real_branch is not None:
1003
if not self._leave_lock:
1004
# If this RemoteBranch will remove the physical lock for the
1005
# repository, make sure the _real_branch doesn't do it
1006
# first. (Because the _real_branch's repository is set to
1007
# be the RemoteRepository.)
1008
self._real_branch.repository.leave_lock_in_place()
1009
self._real_branch.unlock()
1011
# Only write-locked branched need to make a remote method call
1012
# to perfom the unlock.
1014
assert self._lock_token, 'Locked, but no token!'
1015
branch_token = self._lock_token
1016
repo_token = self._repo_lock_token
1017
self._lock_token = None
1018
self._repo_lock_token = None
1019
if not self._leave_lock:
1020
self._unlock(branch_token, repo_token)
1022
def break_lock(self):
1024
return self._real_branch.break_lock()
1026
def leave_lock_in_place(self):
1027
self._leave_lock = True
1029
def dont_leave_lock_in_place(self):
1030
self._leave_lock = False
1032
def last_revision_info(self):
1033
"""See Branch.last_revision_info()."""
1034
path = self.bzrdir._path_for_remote_call(self._client)
1035
response = self._client.call('Branch.last_revision_info', path)
1036
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1037
revno = int(response[1])
1038
last_revision = response[2]
1039
return (revno, last_revision)
1041
def _gen_revision_history(self):
1042
"""See Branch._gen_revision_history()."""
1043
path = self.bzrdir._path_for_remote_call(self._client)
1044
response = self._client.call_expecting_body(
1045
'Branch.revision_history', path)
1046
assert response[0][0] == 'ok', ('unexpected response code %s'
1048
result = response[1].read_body_bytes().split('\x00')
1054
def set_revision_history(self, rev_history):
1055
# Send just the tip revision of the history; the server will generate
1056
# the full history from that. If the revision doesn't exist in this
1057
# branch, NoSuchRevision will be raised.
1058
path = self.bzrdir._path_for_remote_call(self._client)
1059
if rev_history == []:
1062
rev_id = rev_history[-1]
1063
self._clear_cached_state()
1064
response = self._client.call('Branch.set_last_revision',
1065
path, self._lock_token, self._repo_lock_token, rev_id)
1066
if response[0] == 'NoSuchRevision':
1067
raise NoSuchRevision(self, rev_id)
1069
assert response == ('ok',), (
1070
'unexpected response code %r' % (response,))
1071
self._cache_revision_history(rev_history)
1073
def get_parent(self):
1075
return self._real_branch.get_parent()
1077
def set_parent(self, url):
1079
return self._real_branch.set_parent(url)
1081
def get_config(self):
1082
return RemoteBranchConfig(self)
1084
def sprout(self, to_bzrdir, revision_id=None):
1085
# Like Branch.sprout, except that it sprouts a branch in the default
1086
# format, because RemoteBranches can't be created at arbitrary URLs.
1087
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1088
# to_bzrdir.create_branch...
1089
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1090
self.copy_content_into(result, revision_id=revision_id)
1091
result.set_parent(self.bzrdir.root_transport.base)
1095
def pull(self, source, overwrite=False, stop_revision=None,
1097
# FIXME: This asks the real branch to run the hooks, which means
1098
# they're called with the wrong target branch parameter.
1099
# The test suite specifically allows this at present but it should be
1100
# fixed. It should get a _override_hook_target branch,
1101
# as push does. -- mbp 20070405
1103
self._real_branch.pull(
1104
source, overwrite=overwrite, stop_revision=stop_revision,
1108
def push(self, target, overwrite=False, stop_revision=None):
1110
return self._real_branch.push(
1111
target, overwrite=overwrite, stop_revision=stop_revision,
1112
_override_hook_source_branch=self)
1114
def is_locked(self):
1115
return self._lock_count >= 1
1117
def set_last_revision_info(self, revno, revision_id):
1119
self._clear_cached_state()
1120
return self._real_branch.set_last_revision_info(revno, revision_id)
1122
def generate_revision_history(self, revision_id, last_rev=None,
1125
return self._real_branch.generate_revision_history(
1126
revision_id, last_rev=last_rev, other_branch=other_branch)
1131
return self._real_branch.tags
1133
def set_push_location(self, location):
1135
return self._real_branch.set_push_location(location)
1137
def update_revisions(self, other, stop_revision=None):
1139
return self._real_branch.update_revisions(
1140
other, stop_revision=stop_revision)
1143
class RemoteBranchConfig(BranchConfig):
1146
self.branch._ensure_real()
1147
return self.branch._real_branch.get_config().username()
1149
def _get_branch_data_config(self):
1150
self.branch._ensure_real()
1151
if self._branch_data_config is None:
1152
self._branch_data_config = TreeConfig(self.branch._real_branch)
1153
return self._branch_data_config
1156
def _extract_tar(tar, to_dir):
1157
"""Extract all the contents of a tarfile object.
1159
A replacement for extractall, which is not present in python2.4
1162
tar.extract(tarinfo, to_dir)