1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
28
from bzrlib.branch import Branch, BranchReferenceFormat
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
33
from bzrlib.lockable_files import LockableFiles
34
from bzrlib.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
38
# Note: RemoteBzrDirFormat is in bzrdir.py
40
class RemoteBzrDir(BzrDir):
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
43
def __init__(self, transport, _client=None):
44
"""Construct a RemoteBzrDir.
46
:param _client: Private parameter for testing. Disables probing and the
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
50
# this object holds a delegated bzrdir that uses file-level operations
51
# to talk to the other side
52
self._real_bzrdir = None
55
self._medium = transport.get_smart_client()
56
self._client = client._SmartClient(self._medium)
58
self._client = _client
62
path = self._path_for_remote_call(self._client)
63
response = self._client.call('BzrDir.open', path)
64
if response not in [('yes',), ('no',)]:
65
raise errors.UnexpectedSmartServerResponse(response)
66
if response == ('no',):
67
raise errors.NotBranchError(path=transport.base)
69
def _ensure_real(self):
70
"""Ensure that there is a _real_bzrdir set.
72
Used before calls to self._real_bzrdir.
74
if not self._real_bzrdir:
75
self._real_bzrdir = BzrDir.open_from_transport(
76
self.root_transport, _server_formats=False)
78
def create_repository(self, shared=False):
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
83
def create_branch(self):
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
88
def create_workingtree(self, revision_id=None):
89
raise errors.NotLocalUrl(self.transport.base)
91
def find_branch_format(self):
92
"""Find the branch 'format' for this bzrdir.
94
This might be a synthetic object for e.g. RemoteBranch and SVN.
96
b = self.open_branch()
99
def get_branch_reference(self):
100
"""See BzrDir.get_branch_reference()."""
101
path = self._path_for_remote_call(self._client)
102
response = self._client.call('BzrDir.open_branch', path)
103
if response[0] == 'ok':
104
if response[1] == '':
105
# branch at this location.
108
# a branch reference, use the existing BranchReference logic.
110
elif response == ('nobranch',):
111
raise errors.NotBranchError(path=self.root_transport.base)
113
raise errors.UnexpectedSmartServerResponse(response)
115
def open_branch(self, _unsupported=False):
116
assert _unsupported == False, 'unsupported flag support not implemented yet.'
117
reference_url = self.get_branch_reference()
118
if reference_url is None:
119
# branch at this location.
120
return RemoteBranch(self, self.find_repository())
122
# a branch reference, use the existing BranchReference logic.
123
format = BranchReferenceFormat()
124
return format.open(self, _found=True, location=reference_url)
126
def open_repository(self):
127
path = self._path_for_remote_call(self._client)
128
response = self._client.call('BzrDir.find_repository', path)
129
assert response[0] in ('ok', 'norepository'), \
130
'unexpected response code %s' % (response,)
131
if response[0] == 'norepository':
132
raise errors.NoRepositoryPresent(self)
133
assert len(response) == 4, 'incorrect response length %s' % (response,)
134
if response[1] == '':
135
format = RemoteRepositoryFormat()
136
format.rich_root_data = (response[2] == 'yes')
137
format.supports_tree_reference = (response[3] == 'yes')
138
return RemoteRepository(self, format)
140
raise errors.NoRepositoryPresent(self)
142
def open_workingtree(self, recommend_upgrade=True):
144
if self._real_bzrdir.has_workingtree():
145
raise errors.NotLocalUrl(self.root_transport)
147
raise errors.NoWorkingTree(self.root_transport.base)
149
def _path_for_remote_call(self, client):
150
"""Return the path to be used for this bzrdir in a remote call."""
151
return client.remote_path_from_transport(self.root_transport)
153
def get_branch_transport(self, branch_format):
155
return self._real_bzrdir.get_branch_transport(branch_format)
157
def get_repository_transport(self, repository_format):
159
return self._real_bzrdir.get_repository_transport(repository_format)
161
def get_workingtree_transport(self, workingtree_format):
163
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
165
def can_convert_format(self):
166
"""Upgrading of remote bzrdirs is not supported yet."""
169
def needs_format_conversion(self, format=None):
170
"""Upgrading of remote bzrdirs is not supported yet."""
173
def clone(self, url, revision_id=None, force_new_repo=False):
175
return self._real_bzrdir.clone(url, revision_id=revision_id,
176
force_new_repo=force_new_repo)
179
class RemoteRepositoryFormat(repository.RepositoryFormat):
180
"""Format for repositories accessed over a _SmartClient.
182
Instances of this repository are represented by RemoteRepository
185
The RemoteRepositoryFormat is parameterised during construction
186
to reflect the capabilities of the real, remote format. Specifically
187
the attributes rich_root_data and supports_tree_reference are set
188
on a per instance basis, and are not set (and should not be) at
192
_matchingbzrdir = RemoteBzrDirFormat
194
def initialize(self, a_bzrdir, shared=False):
195
assert isinstance(a_bzrdir, RemoteBzrDir), \
196
'%r is not a RemoteBzrDir' % (a_bzrdir,)
197
return a_bzrdir.create_repository(shared=shared)
199
def open(self, a_bzrdir):
200
assert isinstance(a_bzrdir, RemoteBzrDir)
201
return a_bzrdir.open_repository()
203
def get_format_description(self):
204
return 'bzr remote repository'
206
def __eq__(self, other):
207
return self.__class__ == other.__class__
209
def check_conversion_target(self, target_format):
210
if self.rich_root_data and not target_format.rich_root_data:
211
raise errors.BadConversionTarget(
212
'Does not support rich root data.', target_format)
213
if (self.supports_tree_reference and
214
not getattr(target_format, 'supports_tree_reference', False)):
215
raise errors.BadConversionTarget(
216
'Does not support nested trees', target_format)
219
class RemoteRepository(object):
220
"""Repository accessed over rpc.
222
For the moment most operations are performed using local transport-backed
226
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
227
"""Create a RemoteRepository instance.
229
:param remote_bzrdir: The bzrdir hosting this repository.
230
:param format: The RemoteFormat object to use.
231
:param real_repository: If not None, a local implementation of the
232
repository logic for the repository, usually accessing the data
234
:param _client: Private testing parameter - override the smart client
235
to be used by the repository.
238
self._real_repository = real_repository
240
self._real_repository = None
241
self.bzrdir = remote_bzrdir
243
self._client = client._SmartClient(self.bzrdir._medium)
245
self._client = _client
246
self._format = format
247
self._lock_mode = None
248
self._lock_token = None
250
self._leave_lock = False
252
def abort_write_group(self):
253
"""complete a write group on the decorated repository.
255
Smart methods peform operations in a single step so this api
256
is not really applicable except as a compatability thunk
257
for older plugins that don't use e.g. the CommitBuilder
261
return self._real_repository.abort_write_group()
263
def commit_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 compatability thunk
268
for older plugins that don't use e.g. the CommitBuilder
272
return self._real_repository.commit_write_group()
274
def _ensure_real(self):
275
"""Ensure that there is a _real_repository set.
277
Used before calls to self._real_repository.
279
if not self._real_repository:
280
self.bzrdir._ensure_real()
281
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
282
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
284
def get_revision_graph(self, revision_id=None):
285
"""See Repository.get_revision_graph()."""
286
if revision_id is None:
288
elif revision_id == NULL_REVISION:
291
path = self.bzrdir._path_for_remote_call(self._client)
292
assert type(revision_id) is str
293
response = self._client.call_expecting_body(
294
'Repository.get_revision_graph', path, revision_id)
295
if response[0][0] not in ['ok', 'nosuchrevision']:
296
raise errors.UnexpectedSmartServerResponse(response[0])
297
if response[0][0] == 'ok':
298
coded = response[1].read_body_bytes()
300
# no revisions in this repository!
302
lines = coded.split('\n')
305
d = tuple(line.split())
306
revision_graph[d[0]] = d[1:]
308
return revision_graph
310
response_body = response[1].read_body_bytes()
311
assert response_body == ''
312
raise NoSuchRevision(self, revision_id)
314
def has_revision(self, revision_id):
315
"""See Repository.has_revision()."""
316
if revision_id is None:
317
# The null revision is always present.
319
path = self.bzrdir._path_for_remote_call(self._client)
320
response = self._client.call('Repository.has_revision', path, revision_id)
321
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
322
return response[0] == 'yes'
324
def get_graph(self, other_repository=None):
325
"""Return the graph for this repository format"""
326
return self._real_repository.get_graph(other_repository)
328
def gather_stats(self, revid=None, committers=None):
329
"""See Repository.gather_stats()."""
330
path = self.bzrdir._path_for_remote_call(self._client)
331
if revid in (None, NULL_REVISION):
335
if committers is None or not committers:
336
fmt_committers = 'no'
338
fmt_committers = 'yes'
339
response = self._client.call_expecting_body(
340
'Repository.gather_stats', path, fmt_revid, fmt_committers)
341
assert response[0][0] == 'ok', \
342
'unexpected response code %s' % (response[0],)
344
body = response[1].read_body_bytes()
346
for line in body.split('\n'):
349
key, val_text = line.split(':')
350
if key in ('revisions', 'size', 'committers'):
351
result[key] = int(val_text)
352
elif key in ('firstrev', 'latestrev'):
353
values = val_text.split(' ')[1:]
354
result[key] = (float(values[0]), long(values[1]))
358
def get_physical_lock_status(self):
359
"""See Repository.get_physical_lock_status()."""
362
def is_in_write_group(self):
363
"""Return True if there is an open write group.
365
write groups are only applicable locally for the smart server..
367
if self._real_repository:
368
return self._real_repository.is_in_write_group()
371
return self._lock_count >= 1
374
"""See Repository.is_shared()."""
375
path = self.bzrdir._path_for_remote_call(self._client)
376
response = self._client.call('Repository.is_shared', path)
377
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
378
return response[0] == 'yes'
381
# wrong eventually - want a local lock cache context
382
if not self._lock_mode:
383
self._lock_mode = 'r'
385
if self._real_repository is not None:
386
self._real_repository.lock_read()
388
self._lock_count += 1
390
def _remote_lock_write(self, token):
391
path = self.bzrdir._path_for_remote_call(self._client)
394
response = self._client.call('Repository.lock_write', path, token)
395
if response[0] == 'ok':
398
elif response[0] == 'LockContention':
399
raise errors.LockContention('(remote lock)')
400
elif response[0] == 'UnlockableTransport':
401
raise errors.UnlockableTransport(self.bzrdir.root_transport)
403
raise errors.UnexpectedSmartServerResponse(response)
405
def lock_write(self, token=None):
406
if not self._lock_mode:
407
self._lock_token = self._remote_lock_write(token)
408
assert self._lock_token, 'Remote server did not return a token!'
409
if self._real_repository is not None:
410
self._real_repository.lock_write(token=self._lock_token)
411
if token is not None:
412
self._leave_lock = True
414
self._leave_lock = False
415
self._lock_mode = 'w'
417
elif self._lock_mode == 'r':
418
raise errors.ReadOnlyError(self)
420
self._lock_count += 1
421
return self._lock_token
423
def leave_lock_in_place(self):
424
self._leave_lock = True
426
def dont_leave_lock_in_place(self):
427
self._leave_lock = False
429
def _set_real_repository(self, repository):
430
"""Set the _real_repository for this repository.
432
:param repository: The repository to fallback to for non-hpss
433
implemented operations.
435
assert not isinstance(repository, RemoteRepository)
436
self._real_repository = repository
437
if self._lock_mode == 'w':
438
# if we are already locked, the real repository must be able to
439
# acquire the lock with our token.
440
self._real_repository.lock_write(self._lock_token)
441
elif self._lock_mode == 'r':
442
self._real_repository.lock_read()
444
def start_write_group(self):
445
"""Start a write group on the decorated repository.
447
Smart methods peform operations in a single step so this api
448
is not really applicable except as a compatability thunk
449
for older plugins that don't use e.g. the CommitBuilder
453
return self._real_repository.start_write_group()
455
def _unlock(self, token):
456
path = self.bzrdir._path_for_remote_call(self._client)
457
response = self._client.call('Repository.unlock', path, token)
458
if response == ('ok',):
460
elif response[0] == 'TokenMismatch':
461
raise errors.TokenMismatch(token, '(remote token)')
463
raise errors.UnexpectedSmartServerResponse(response)
466
if self._lock_count == 1 and self._lock_mode == 'w':
467
# don't unlock if inside a write group.
468
if self.is_in_write_group():
469
raise errors.BzrError(
470
'Must end write groups before releasing write locks.')
471
self._lock_count -= 1
472
if not self._lock_count:
473
mode = self._lock_mode
474
self._lock_mode = None
475
if self._real_repository is not None:
476
self._real_repository.unlock()
478
# Only write-locked repositories need to make a remote method
479
# call to perfom the unlock.
481
assert self._lock_token, 'Locked, but no token!'
482
token = self._lock_token
483
self._lock_token = None
484
if not self._leave_lock:
487
def break_lock(self):
488
# should hand off to the network
490
return self._real_repository.break_lock()
492
def _get_tarball(self, compression):
493
"""Return a TemporaryFile containing a repository tarball"""
495
path = self.bzrdir._path_for_remote_call(self._client)
496
response, protocol = self._client.call_expecting_body(
497
'Repository.tarball', path, compression)
498
assert response[0] in ('ok', 'failure'), \
499
'unexpected response code %s' % (response,)
500
if response[0] == 'ok':
501
# Extract the tarball and return it
502
t = tempfile.NamedTemporaryFile()
503
# TODO: rpc layer should read directly into it...
504
t.write(protocol.read_body_bytes())
508
raise errors.SmartServerError(error_code=response)
510
def sprout(self, to_bzrdir, revision_id=None):
511
# TODO: Option to control what format is created?
512
to_repo = to_bzrdir.create_repository()
513
self._copy_repository_tarball(to_repo, revision_id)
516
### These methods are just thin shims to the VFS object for now.
518
def revision_tree(self, revision_id):
520
return self._real_repository.revision_tree(revision_id)
522
def get_commit_builder(self, branch, parents, config, timestamp=None,
523
timezone=None, committer=None, revprops=None,
525
# FIXME: It ought to be possible to call this without immediately
526
# triggering _ensure_real. For now it's the easiest thing to do.
528
builder = self._real_repository.get_commit_builder(branch, parents,
529
config, timestamp=timestamp, timezone=timezone,
530
committer=committer, revprops=revprops, revision_id=revision_id)
531
# Make the builder use this RemoteRepository rather than the real one.
532
builder.repository = self
536
def add_inventory(self, revid, inv, parents):
538
return self._real_repository.add_inventory(revid, inv, parents)
541
def add_revision(self, rev_id, rev, inv=None, config=None):
543
return self._real_repository.add_revision(
544
rev_id, rev, inv=inv, config=config)
547
def get_inventory(self, revision_id):
549
return self._real_repository.get_inventory(revision_id)
552
def get_revision(self, revision_id):
554
return self._real_repository.get_revision(revision_id)
557
def weave_store(self):
559
return self._real_repository.weave_store
561
def get_transaction(self):
563
return self._real_repository.get_transaction()
566
def clone(self, a_bzrdir, revision_id=None):
568
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
570
def make_working_trees(self):
571
"""RemoteRepositories never create working trees by default."""
574
def fetch(self, source, revision_id=None, pb=None):
576
return self._real_repository.fetch(
577
source, revision_id=revision_id, pb=pb)
580
def control_weaves(self):
582
return self._real_repository.control_weaves
585
def get_ancestry(self, revision_id, topo_sorted=True):
587
return self._real_repository.get_ancestry(revision_id, topo_sorted)
590
def get_inventory_weave(self):
592
return self._real_repository.get_inventory_weave()
594
def fileids_altered_by_revision_ids(self, revision_ids):
596
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
599
def get_signature_text(self, revision_id):
601
return self._real_repository.get_signature_text(revision_id)
604
def get_revision_graph_with_ghosts(self, revision_ids=None):
606
return self._real_repository.get_revision_graph_with_ghosts(
607
revision_ids=revision_ids)
610
def get_inventory_xml(self, revision_id):
612
return self._real_repository.get_inventory_xml(revision_id)
614
def deserialise_inventory(self, revision_id, xml):
616
return self._real_repository.deserialise_inventory(revision_id, xml)
618
def reconcile(self, other=None, thorough=False):
620
return self._real_repository.reconcile(other=other, thorough=thorough)
622
def all_revision_ids(self):
624
return self._real_repository.all_revision_ids()
627
def get_deltas_for_revisions(self, revisions):
629
return self._real_repository.get_deltas_for_revisions(revisions)
632
def get_revision_delta(self, revision_id):
634
return self._real_repository.get_revision_delta(revision_id)
637
def revision_trees(self, revision_ids):
639
return self._real_repository.revision_trees(revision_ids)
642
def get_revision_reconcile(self, revision_id):
644
return self._real_repository.get_revision_reconcile(revision_id)
647
def check(self, revision_ids):
649
return self._real_repository.check(revision_ids)
651
def copy_content_into(self, destination, revision_id=None):
653
return self._real_repository.copy_content_into(
654
destination, revision_id=revision_id)
656
def _copy_repository_tarball(self, destination, revision_id=None):
657
# get a tarball of the remote repository, and copy from that into the
659
from bzrlib import osutils
662
from StringIO import StringIO
663
# TODO: Maybe a progress bar while streaming the tarball?
664
note("Copying repository content as tarball...")
665
tar_file = self._get_tarball('bz2')
667
tar = tarfile.open('repository', fileobj=tar_file,
669
tmpdir = tempfile.mkdtemp()
671
_extract_tar(tar, tmpdir)
672
tmp_bzrdir = BzrDir.open(tmpdir)
673
tmp_repo = tmp_bzrdir.open_repository()
674
tmp_repo.copy_content_into(destination, revision_id)
676
osutils.rmtree(tmpdir)
679
# TODO: if the server doesn't support this operation, maybe do it the
680
# slow way using the _real_repository?
682
# TODO: Suggestion from john: using external tar is much faster than
683
# python's tarfile library, but it may not work on windows.
687
"""Compress the data within the repository.
689
This is not currently implemented within the smart server.
692
return self._real_repository.pack()
694
def set_make_working_trees(self, new_value):
695
raise NotImplementedError(self.set_make_working_trees)
698
def sign_revision(self, revision_id, gpg_strategy):
700
return self._real_repository.sign_revision(revision_id, gpg_strategy)
703
def get_revisions(self, revision_ids):
705
return self._real_repository.get_revisions(revision_ids)
707
def supports_rich_root(self):
709
return self._real_repository.supports_rich_root()
711
def iter_reverse_revision_history(self, revision_id):
713
return self._real_repository.iter_reverse_revision_history(revision_id)
716
def _serializer(self):
718
return self._real_repository._serializer
720
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
722
return self._real_repository.store_revision_signature(
723
gpg_strategy, plaintext, revision_id)
725
def has_signature_for_revision_id(self, revision_id):
727
return self._real_repository.has_signature_for_revision_id(revision_id)
730
class RemoteBranchLockableFiles(LockableFiles):
731
"""A 'LockableFiles' implementation that talks to a smart server.
733
This is not a public interface class.
736
def __init__(self, bzrdir, _client):
738
self._client = _client
739
self._need_find_modes = True
740
LockableFiles.__init__(
741
self, bzrdir.get_branch_transport(None),
742
'lock', lockdir.LockDir)
744
def _find_modes(self):
745
# RemoteBranches don't let the client set the mode of control files.
746
self._dir_mode = None
747
self._file_mode = None
750
"""'get' a remote path as per the LockableFiles interface.
752
:param path: the file to 'get'. If this is 'branch.conf', we do not
753
just retrieve a file, instead we ask the smart server to generate
754
a configuration for us - which is retrieved as an INI file.
756
if path == 'branch.conf':
757
path = self.bzrdir._path_for_remote_call(self._client)
758
response = self._client.call_expecting_body(
759
'Branch.get_config_file', path)
760
assert response[0][0] == 'ok', \
761
'unexpected response code %s' % (response[0],)
762
return StringIO(response[1].read_body_bytes())
765
return LockableFiles.get(self, path)
768
class RemoteBranchFormat(branch.BranchFormat):
770
def __eq__(self, other):
771
return (isinstance(other, RemoteBranchFormat) and
772
self.__dict__ == other.__dict__)
774
def get_format_description(self):
775
return 'Remote BZR Branch'
777
def get_format_string(self):
778
return 'Remote BZR Branch'
780
def open(self, a_bzrdir):
781
assert isinstance(a_bzrdir, RemoteBzrDir)
782
return a_bzrdir.open_branch()
784
def initialize(self, a_bzrdir):
785
assert isinstance(a_bzrdir, RemoteBzrDir)
786
return a_bzrdir.create_branch()
789
class RemoteBranch(branch.Branch):
790
"""Branch stored on a server accessed by HPSS RPC.
792
At the moment most operations are mapped down to simple file operations.
795
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
797
"""Create a RemoteBranch instance.
799
:param real_branch: An optional local implementation of the branch
800
format, usually accessing the data via the VFS.
801
:param _client: Private parameter for testing.
803
# We intentionally don't call the parent class's __init__, because it
804
# will try to assign to self.tags, which is a property in this subclass.
805
# And the parent's __init__ doesn't do much anyway.
806
self._revision_history_cache = None
807
self.bzrdir = remote_bzrdir
808
if _client is not None:
809
self._client = _client
811
self._client = client._SmartClient(self.bzrdir._medium)
812
self.repository = remote_repository
813
if real_branch is not None:
814
self._real_branch = real_branch
815
# Give the remote repository the matching real repo.
816
real_repo = self._real_branch.repository
817
if isinstance(real_repo, RemoteRepository):
818
real_repo._ensure_real()
819
real_repo = real_repo._real_repository
820
self.repository._set_real_repository(real_repo)
821
# Give the branch the remote repository to let fast-pathing happen.
822
self._real_branch.repository = self.repository
824
self._real_branch = None
825
# Fill out expected attributes of branch for bzrlib api users.
826
self._format = RemoteBranchFormat()
827
self.base = self.bzrdir.root_transport.base
828
self._control_files = None
829
self._lock_mode = None
830
self._lock_token = None
832
self._leave_lock = False
835
return "%s(%s)" % (self.__class__.__name__, self.base)
839
def _ensure_real(self):
840
"""Ensure that there is a _real_branch set.
842
Used before calls to self._real_branch.
844
if not self._real_branch:
845
assert vfs.vfs_enabled()
846
self.bzrdir._ensure_real()
847
self._real_branch = self.bzrdir._real_bzrdir.open_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
856
# XXX: deal with _lock_mode == 'w'
857
if self._lock_mode == 'r':
858
self._real_branch.lock_read()
861
def control_files(self):
862
# Defer actually creating RemoteBranchLockableFiles until its needed,
863
# because it triggers an _ensure_real that we otherwise might not need.
864
if self._control_files is None:
865
self._control_files = RemoteBranchLockableFiles(
866
self.bzrdir, self._client)
867
return self._control_files
869
def _get_checkout_format(self):
871
return self._real_branch._get_checkout_format()
873
def get_physical_lock_status(self):
874
"""See Branch.get_physical_lock_status()."""
875
# should be an API call to the server, as branches must be lockable.
877
return self._real_branch.get_physical_lock_status()
880
if not self._lock_mode:
881
self._lock_mode = 'r'
883
if self._real_branch is not None:
884
self._real_branch.lock_read()
886
self._lock_count += 1
888
def _remote_lock_write(self, token):
890
branch_token = repo_token = ''
893
repo_token = self.repository.lock_write()
894
self.repository.unlock()
895
path = self.bzrdir._path_for_remote_call(self._client)
896
response = self._client.call('Branch.lock_write', path, branch_token,
898
if response[0] == 'ok':
899
ok, branch_token, repo_token = response
900
return branch_token, repo_token
901
elif response[0] == 'LockContention':
902
raise errors.LockContention('(remote lock)')
903
elif response[0] == 'TokenMismatch':
904
raise errors.TokenMismatch(token, '(remote token)')
905
elif response[0] == 'UnlockableTransport':
906
raise errors.UnlockableTransport(self.bzrdir.root_transport)
907
elif response[0] == 'ReadOnlyError':
908
raise errors.ReadOnlyError(self)
910
raise errors.UnexpectedSmartServerResponse(response)
912
def lock_write(self, token=None):
913
if not self._lock_mode:
914
remote_tokens = self._remote_lock_write(token)
915
self._lock_token, self._repo_lock_token = remote_tokens
916
assert self._lock_token, 'Remote server did not return a token!'
917
# TODO: We really, really, really don't want to call _ensure_real
918
# here, but it's the easiest way to ensure coherency between the
919
# state of the RemoteBranch and RemoteRepository objects and the
920
# physical locks. If we don't materialise the real objects here,
921
# then getting everything in the right state later is complex, so
922
# for now we just do it the lazy way.
923
# -- Andrew Bennetts, 2007-02-22.
925
if self._real_branch is not None:
926
self._real_branch.repository.lock_write(
927
token=self._repo_lock_token)
929
self._real_branch.lock_write(token=self._lock_token)
931
self._real_branch.repository.unlock()
932
if token is not None:
933
self._leave_lock = True
935
# XXX: this case seems to be unreachable; token cannot be None.
936
self._leave_lock = False
937
self._lock_mode = 'w'
939
elif self._lock_mode == 'r':
940
raise errors.ReadOnlyTransaction
942
if token is not None:
943
# A token was given to lock_write, and we're relocking, so check
944
# that the given token actually matches the one we already have.
945
if token != self._lock_token:
946
raise errors.TokenMismatch(token, self._lock_token)
947
self._lock_count += 1
948
return self._lock_token
950
def _unlock(self, branch_token, repo_token):
951
path = self.bzrdir._path_for_remote_call(self._client)
952
response = self._client.call('Branch.unlock', path, branch_token,
954
if response == ('ok',):
956
elif response[0] == 'TokenMismatch':
957
raise errors.TokenMismatch(
958
str((branch_token, repo_token)), '(remote tokens)')
960
raise errors.UnexpectedSmartServerResponse(response)
963
self._lock_count -= 1
964
if not self._lock_count:
965
self._clear_cached_state()
966
mode = self._lock_mode
967
self._lock_mode = None
968
if self._real_branch is not None:
969
if not self._leave_lock:
970
# If this RemoteBranch will remove the physical lock for the
971
# repository, make sure the _real_branch doesn't do it
972
# first. (Because the _real_branch's repository is set to
973
# be the RemoteRepository.)
974
self._real_branch.repository.leave_lock_in_place()
975
self._real_branch.unlock()
977
# Only write-locked branched need to make a remote method call
978
# to perfom the unlock.
980
assert self._lock_token, 'Locked, but no token!'
981
branch_token = self._lock_token
982
repo_token = self._repo_lock_token
983
self._lock_token = None
984
self._repo_lock_token = None
985
if not self._leave_lock:
986
self._unlock(branch_token, repo_token)
988
def break_lock(self):
990
return self._real_branch.break_lock()
992
def leave_lock_in_place(self):
993
self._leave_lock = True
995
def dont_leave_lock_in_place(self):
996
self._leave_lock = False
998
def last_revision_info(self):
999
"""See Branch.last_revision_info()."""
1000
path = self.bzrdir._path_for_remote_call(self._client)
1001
response = self._client.call('Branch.last_revision_info', path)
1002
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1003
revno = int(response[1])
1004
last_revision = response[2]
1005
return (revno, last_revision)
1007
def _gen_revision_history(self):
1008
"""See Branch._gen_revision_history()."""
1009
path = self.bzrdir._path_for_remote_call(self._client)
1010
response = self._client.call_expecting_body(
1011
'Branch.revision_history', path)
1012
assert response[0][0] == 'ok', ('unexpected response code %s'
1014
result = response[1].read_body_bytes().split('\x00')
1020
def set_revision_history(self, rev_history):
1021
# Send just the tip revision of the history; the server will generate
1022
# the full history from that. If the revision doesn't exist in this
1023
# branch, NoSuchRevision will be raised.
1024
path = self.bzrdir._path_for_remote_call(self._client)
1025
if rev_history == []:
1028
rev_id = rev_history[-1]
1029
self._clear_cached_state()
1030
response = self._client.call('Branch.set_last_revision',
1031
path, self._lock_token, self._repo_lock_token, rev_id)
1032
if response[0] == 'NoSuchRevision':
1033
raise NoSuchRevision(self, rev_id)
1035
assert response == ('ok',), (
1036
'unexpected response code %r' % (response,))
1037
self._cache_revision_history(rev_history)
1039
def get_parent(self):
1041
return self._real_branch.get_parent()
1043
def set_parent(self, url):
1045
return self._real_branch.set_parent(url)
1047
def get_config(self):
1048
return RemoteBranchConfig(self)
1050
def sprout(self, to_bzrdir, revision_id=None):
1051
# Like Branch.sprout, except that it sprouts a branch in the default
1052
# format, because RemoteBranches can't be created at arbitrary URLs.
1053
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1054
# to_bzrdir.create_branch...
1055
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1056
self.copy_content_into(result, revision_id=revision_id)
1057
result.set_parent(self.bzrdir.root_transport.base)
1061
def append_revision(self, *revision_ids):
1063
return self._real_branch.append_revision(*revision_ids)
1066
def pull(self, source, overwrite=False, stop_revision=None,
1068
# FIXME: This asks the real branch to run the hooks, which means
1069
# they're called with the wrong target branch parameter.
1070
# The test suite specifically allows this at present but it should be
1071
# fixed. It should get a _override_hook_target branch,
1072
# as push does. -- mbp 20070405
1074
self._real_branch.pull(
1075
source, overwrite=overwrite, stop_revision=stop_revision,
1079
def push(self, target, overwrite=False, stop_revision=None):
1081
return self._real_branch.push(
1082
target, overwrite=overwrite, stop_revision=stop_revision,
1083
_override_hook_source_branch=self)
1085
def is_locked(self):
1086
return self._lock_count >= 1
1088
def set_last_revision_info(self, revno, revision_id):
1090
self._clear_cached_state()
1091
return self._real_branch.set_last_revision_info(revno, revision_id)
1093
def generate_revision_history(self, revision_id, last_rev=None,
1096
return self._real_branch.generate_revision_history(
1097
revision_id, last_rev=last_rev, other_branch=other_branch)
1102
return self._real_branch.tags
1104
def set_push_location(self, location):
1106
return self._real_branch.set_push_location(location)
1108
def update_revisions(self, other, stop_revision=None):
1110
return self._real_branch.update_revisions(
1111
other, stop_revision=stop_revision)
1114
class RemoteBranchConfig(BranchConfig):
1117
self.branch._ensure_real()
1118
return self.branch._real_branch.get_config().username()
1120
def _get_branch_data_config(self):
1121
self.branch._ensure_real()
1122
if self._branch_data_config is None:
1123
self._branch_data_config = TreeConfig(self.branch._real_branch)
1124
return self._branch_data_config
1127
def _extract_tar(tar, to_dir):
1128
"""Extract all the contents of a tarfile object.
1130
A replacement for extractall, which is not present in python2.4
1133
tar.extract(tarinfo, to_dir)