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 destroy_repository(self):
88
"""See BzrDir.destroy_repository"""
90
self._real_bzrdir.destroy_repository()
92
def create_branch(self):
94
real_branch = self._real_bzrdir.create_branch()
95
return RemoteBranch(self, self.find_repository(), real_branch)
97
def destroy_branch(self):
98
"""See BzrDir.destroy_branch"""
100
self._real_bzrdir.destroy_branch()
102
def create_workingtree(self, revision_id=None):
103
raise errors.NotLocalUrl(self.transport.base)
105
def find_branch_format(self):
106
"""Find the branch 'format' for this bzrdir.
108
This might be a synthetic object for e.g. RemoteBranch and SVN.
110
b = self.open_branch()
113
def get_branch_reference(self):
114
"""See BzrDir.get_branch_reference()."""
115
path = self._path_for_remote_call(self._client)
116
response = self._client.call('BzrDir.open_branch', path)
117
if response[0] == 'ok':
118
if response[1] == '':
119
# branch at this location.
122
# a branch reference, use the existing BranchReference logic.
124
elif response == ('nobranch',):
125
raise errors.NotBranchError(path=self.root_transport.base)
127
raise errors.UnexpectedSmartServerResponse(response)
129
def open_branch(self, _unsupported=False):
130
assert _unsupported == False, 'unsupported flag support not implemented yet.'
131
reference_url = self.get_branch_reference()
132
if reference_url is None:
133
# branch at this location.
134
return RemoteBranch(self, self.find_repository())
136
# a branch reference, use the existing BranchReference logic.
137
format = BranchReferenceFormat()
138
return format.open(self, _found=True, location=reference_url)
140
def open_repository(self):
141
path = self._path_for_remote_call(self._client)
142
response = self._client.call('BzrDir.find_repository', path)
143
assert response[0] in ('ok', 'norepository'), \
144
'unexpected response code %s' % (response,)
145
if response[0] == 'norepository':
146
raise errors.NoRepositoryPresent(self)
147
assert len(response) == 4, 'incorrect response length %s' % (response,)
148
if response[1] == '':
149
format = RemoteRepositoryFormat()
150
format.rich_root_data = (response[2] == 'yes')
151
format.supports_tree_reference = (response[3] == 'yes')
152
return RemoteRepository(self, format)
154
raise errors.NoRepositoryPresent(self)
156
def open_workingtree(self, recommend_upgrade=True):
158
if self._real_bzrdir.has_workingtree():
159
raise errors.NotLocalUrl(self.root_transport)
161
raise errors.NoWorkingTree(self.root_transport.base)
163
def _path_for_remote_call(self, client):
164
"""Return the path to be used for this bzrdir in a remote call."""
165
return client.remote_path_from_transport(self.root_transport)
167
def get_branch_transport(self, branch_format):
169
return self._real_bzrdir.get_branch_transport(branch_format)
171
def get_repository_transport(self, repository_format):
173
return self._real_bzrdir.get_repository_transport(repository_format)
175
def get_workingtree_transport(self, workingtree_format):
177
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
179
def can_convert_format(self):
180
"""Upgrading of remote bzrdirs is not supported yet."""
183
def needs_format_conversion(self, format=None):
184
"""Upgrading of remote bzrdirs is not supported yet."""
187
def clone(self, url, revision_id=None, force_new_repo=False):
189
return self._real_bzrdir.clone(url, revision_id=revision_id,
190
force_new_repo=force_new_repo)
193
class RemoteRepositoryFormat(repository.RepositoryFormat):
194
"""Format for repositories accessed over a _SmartClient.
196
Instances of this repository are represented by RemoteRepository
199
The RemoteRepositoryFormat is parameterised during construction
200
to reflect the capabilities of the real, remote format. Specifically
201
the attributes rich_root_data and supports_tree_reference are set
202
on a per instance basis, and are not set (and should not be) at
206
_matchingbzrdir = RemoteBzrDirFormat
208
def initialize(self, a_bzrdir, shared=False):
209
assert isinstance(a_bzrdir, RemoteBzrDir), \
210
'%r is not a RemoteBzrDir' % (a_bzrdir,)
211
return a_bzrdir.create_repository(shared=shared)
213
def open(self, a_bzrdir):
214
assert isinstance(a_bzrdir, RemoteBzrDir)
215
return a_bzrdir.open_repository()
217
def get_format_description(self):
218
return 'bzr remote repository'
220
def __eq__(self, other):
221
return self.__class__ == other.__class__
223
def check_conversion_target(self, target_format):
224
if self.rich_root_data and not target_format.rich_root_data:
225
raise errors.BadConversionTarget(
226
'Does not support rich root data.', target_format)
227
if (self.supports_tree_reference and
228
not getattr(target_format, 'supports_tree_reference', False)):
229
raise errors.BadConversionTarget(
230
'Does not support nested trees', target_format)
233
class RemoteRepository(object):
234
"""Repository accessed over rpc.
236
For the moment most operations are performed using local transport-backed
240
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
241
"""Create a RemoteRepository instance.
243
:param remote_bzrdir: The bzrdir hosting this repository.
244
:param format: The RemoteFormat object to use.
245
:param real_repository: If not None, a local implementation of the
246
repository logic for the repository, usually accessing the data
248
:param _client: Private testing parameter - override the smart client
249
to be used by the repository.
252
self._real_repository = real_repository
254
self._real_repository = None
255
self.bzrdir = remote_bzrdir
257
self._client = client._SmartClient(self.bzrdir._shared_medium)
259
self._client = _client
260
self._format = format
261
self._lock_mode = None
262
self._lock_token = None
264
self._leave_lock = False
266
self._reconcile_does_inventory_gc = True
268
def abort_write_group(self):
269
"""Complete a write group on the decorated repository.
271
Smart methods peform operations in a single step so this api
272
is not really applicable except as a compatibility thunk
273
for older plugins that don't use e.g. the CommitBuilder
277
return self._real_repository.abort_write_group()
279
def commit_write_group(self):
280
"""Complete a write group on the decorated repository.
282
Smart methods peform operations in a single step so this api
283
is not really applicable except as a compatibility thunk
284
for older plugins that don't use e.g. the CommitBuilder
288
return self._real_repository.commit_write_group()
290
def _ensure_real(self):
291
"""Ensure that there is a _real_repository set.
293
Used before calls to self._real_repository.
295
if not self._real_repository:
296
self.bzrdir._ensure_real()
297
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
298
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
300
def get_revision_graph(self, revision_id=None):
301
"""See Repository.get_revision_graph()."""
302
if revision_id is None:
304
elif revision_id == NULL_REVISION:
307
path = self.bzrdir._path_for_remote_call(self._client)
308
assert type(revision_id) is str
309
response = self._client.call_expecting_body(
310
'Repository.get_revision_graph', path, revision_id)
311
if response[0][0] not in ['ok', 'nosuchrevision']:
312
raise errors.UnexpectedSmartServerResponse(response[0])
313
if response[0][0] == 'ok':
314
coded = response[1].read_body_bytes()
316
# no revisions in this repository!
318
lines = coded.split('\n')
321
d = tuple(line.split())
322
revision_graph[d[0]] = d[1:]
324
return revision_graph
326
response_body = response[1].read_body_bytes()
327
assert response_body == ''
328
raise NoSuchRevision(self, revision_id)
330
def has_revision(self, revision_id):
331
"""See Repository.has_revision()."""
332
if revision_id is None:
333
# The null revision is always present.
335
path = self.bzrdir._path_for_remote_call(self._client)
336
response = self._client.call('Repository.has_revision', path, revision_id)
337
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
338
return response[0] == 'yes'
340
def has_same_location(self, other):
341
return (self.__class__ == other.__class__ and
342
self.bzrdir.transport.base == other.bzrdir.transport.base)
344
def get_graph(self, other_repository=None):
345
"""Return the graph for this repository format"""
346
return self._real_repository.get_graph(other_repository)
348
def gather_stats(self, revid=None, committers=None):
349
"""See Repository.gather_stats()."""
350
path = self.bzrdir._path_for_remote_call(self._client)
351
if revid in (None, NULL_REVISION):
355
if committers is None or not committers:
356
fmt_committers = 'no'
358
fmt_committers = 'yes'
359
response = self._client.call_expecting_body(
360
'Repository.gather_stats', path, fmt_revid, fmt_committers)
361
assert response[0][0] == 'ok', \
362
'unexpected response code %s' % (response[0],)
364
body = response[1].read_body_bytes()
366
for line in body.split('\n'):
369
key, val_text = line.split(':')
370
if key in ('revisions', 'size', 'committers'):
371
result[key] = int(val_text)
372
elif key in ('firstrev', 'latestrev'):
373
values = val_text.split(' ')[1:]
374
result[key] = (float(values[0]), long(values[1]))
378
def get_physical_lock_status(self):
379
"""See Repository.get_physical_lock_status()."""
382
def is_in_write_group(self):
383
"""Return True if there is an open write group.
385
write groups are only applicable locally for the smart server..
387
if self._real_repository:
388
return self._real_repository.is_in_write_group()
391
return self._lock_count >= 1
394
"""See Repository.is_shared()."""
395
path = self.bzrdir._path_for_remote_call(self._client)
396
response = self._client.call('Repository.is_shared', path)
397
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
398
return response[0] == 'yes'
401
# wrong eventually - want a local lock cache context
402
if not self._lock_mode:
403
self._lock_mode = 'r'
405
if self._real_repository is not None:
406
self._real_repository.lock_read()
408
self._lock_count += 1
410
def _remote_lock_write(self, token):
411
path = self.bzrdir._path_for_remote_call(self._client)
414
response = self._client.call('Repository.lock_write', path, token)
415
if response[0] == 'ok':
418
elif response[0] == 'LockContention':
419
raise errors.LockContention('(remote lock)')
420
elif response[0] == 'UnlockableTransport':
421
raise errors.UnlockableTransport(self.bzrdir.root_transport)
423
raise errors.UnexpectedSmartServerResponse(response)
425
def lock_write(self, token=None):
426
if not self._lock_mode:
427
self._lock_token = self._remote_lock_write(token)
428
assert self._lock_token, 'Remote server did not return a token!'
429
if self._real_repository is not None:
430
self._real_repository.lock_write(token=self._lock_token)
431
if token is not None:
432
self._leave_lock = True
434
self._leave_lock = False
435
self._lock_mode = 'w'
437
elif self._lock_mode == 'r':
438
raise errors.ReadOnlyError(self)
440
self._lock_count += 1
441
return self._lock_token
443
def leave_lock_in_place(self):
444
self._leave_lock = True
446
def dont_leave_lock_in_place(self):
447
self._leave_lock = False
449
def _set_real_repository(self, repository):
450
"""Set the _real_repository for this repository.
452
:param repository: The repository to fallback to for non-hpss
453
implemented operations.
455
assert not isinstance(repository, RemoteRepository)
456
self._real_repository = repository
457
if self._lock_mode == 'w':
458
# if we are already locked, the real repository must be able to
459
# acquire the lock with our token.
460
self._real_repository.lock_write(self._lock_token)
461
elif self._lock_mode == 'r':
462
self._real_repository.lock_read()
464
def start_write_group(self):
465
"""Start a write group on the decorated repository.
467
Smart methods peform operations in a single step so this api
468
is not really applicable except as a compatibility thunk
469
for older plugins that don't use e.g. the CommitBuilder
473
return self._real_repository.start_write_group()
475
def _unlock(self, token):
476
path = self.bzrdir._path_for_remote_call(self._client)
477
response = self._client.call('Repository.unlock', path, token)
478
if response == ('ok',):
480
elif response[0] == 'TokenMismatch':
481
raise errors.TokenMismatch(token, '(remote token)')
483
raise errors.UnexpectedSmartServerResponse(response)
486
if self._lock_count == 1 and self._lock_mode == 'w':
487
# don't unlock if inside a write group.
488
if self.is_in_write_group():
489
raise errors.BzrError(
490
'Must end write groups before releasing write locks.')
491
self._lock_count -= 1
492
if not self._lock_count:
493
mode = self._lock_mode
494
self._lock_mode = None
495
if self._real_repository is not None:
496
self._real_repository.unlock()
498
# Only write-locked repositories need to make a remote method
499
# call to perfom the unlock.
501
assert self._lock_token, 'Locked, but no token!'
502
token = self._lock_token
503
self._lock_token = None
504
if not self._leave_lock:
507
def break_lock(self):
508
# should hand off to the network
510
return self._real_repository.break_lock()
512
def _get_tarball(self, compression):
513
"""Return a TemporaryFile containing a repository tarball"""
515
path = self.bzrdir._path_for_remote_call(self._client)
516
response, protocol = self._client.call_expecting_body(
517
'Repository.tarball', path, compression)
518
assert response[0] in ('ok', 'failure'), \
519
'unexpected response code %s' % (response,)
520
if response[0] == 'ok':
521
# Extract the tarball and return it
522
t = tempfile.NamedTemporaryFile()
523
# TODO: rpc layer should read directly into it...
524
t.write(protocol.read_body_bytes())
528
raise errors.SmartServerError(error_code=response)
530
def sprout(self, to_bzrdir, revision_id=None):
531
# TODO: Option to control what format is created?
532
to_repo = to_bzrdir.create_repository()
533
self._copy_repository_tarball(to_repo, revision_id)
536
### These methods are just thin shims to the VFS object for now.
538
def revision_tree(self, revision_id):
540
return self._real_repository.revision_tree(revision_id)
542
def get_serializer_format(self):
544
return self._real_repository.get_serializer_format()
546
def get_commit_builder(self, branch, parents, config, timestamp=None,
547
timezone=None, committer=None, revprops=None,
549
# FIXME: It ought to be possible to call this without immediately
550
# triggering _ensure_real. For now it's the easiest thing to do.
552
builder = self._real_repository.get_commit_builder(branch, parents,
553
config, timestamp=timestamp, timezone=timezone,
554
committer=committer, revprops=revprops, revision_id=revision_id)
555
# Make the builder use this RemoteRepository rather than the real one.
556
builder.repository = self
560
def add_inventory(self, revid, inv, parents):
562
return self._real_repository.add_inventory(revid, inv, parents)
565
def add_revision(self, rev_id, rev, inv=None, config=None):
567
return self._real_repository.add_revision(
568
rev_id, rev, inv=inv, config=config)
571
def get_inventory(self, revision_id):
573
return self._real_repository.get_inventory(revision_id)
576
def get_revision(self, revision_id):
578
return self._real_repository.get_revision(revision_id)
581
def weave_store(self):
583
return self._real_repository.weave_store
585
def get_transaction(self):
587
return self._real_repository.get_transaction()
590
def clone(self, a_bzrdir, revision_id=None):
592
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
594
def make_working_trees(self):
595
"""RemoteRepositories never create working trees by default."""
598
def fetch(self, source, revision_id=None, pb=None):
600
return self._real_repository.fetch(
601
source, revision_id=revision_id, pb=pb)
603
def create_bundle(self, target, base, fileobj, format=None):
605
self._real_repository.create_bundle(target, base, fileobj, format)
608
def control_weaves(self):
610
return self._real_repository.control_weaves
613
def get_ancestry(self, revision_id, topo_sorted=True):
615
return self._real_repository.get_ancestry(revision_id, topo_sorted)
618
def get_inventory_weave(self):
620
return self._real_repository.get_inventory_weave()
622
def fileids_altered_by_revision_ids(self, revision_ids):
624
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
626
def iter_files_bytes(self, desired_files):
627
"""See Repository.iter_file_bytes.
630
return self._real_repository.iter_files_bytes(desired_files)
633
def get_signature_text(self, revision_id):
635
return self._real_repository.get_signature_text(revision_id)
638
def get_revision_graph_with_ghosts(self, revision_ids=None):
640
return self._real_repository.get_revision_graph_with_ghosts(
641
revision_ids=revision_ids)
644
def get_inventory_xml(self, revision_id):
646
return self._real_repository.get_inventory_xml(revision_id)
648
def deserialise_inventory(self, revision_id, xml):
650
return self._real_repository.deserialise_inventory(revision_id, xml)
652
def reconcile(self, other=None, thorough=False):
654
return self._real_repository.reconcile(other=other, thorough=thorough)
656
def all_revision_ids(self):
658
return self._real_repository.all_revision_ids()
661
def get_deltas_for_revisions(self, revisions):
663
return self._real_repository.get_deltas_for_revisions(revisions)
666
def get_revision_delta(self, revision_id):
668
return self._real_repository.get_revision_delta(revision_id)
671
def revision_trees(self, revision_ids):
673
return self._real_repository.revision_trees(revision_ids)
676
def get_revision_reconcile(self, revision_id):
678
return self._real_repository.get_revision_reconcile(revision_id)
681
def check(self, revision_ids):
683
return self._real_repository.check(revision_ids)
685
def copy_content_into(self, destination, revision_id=None):
687
return self._real_repository.copy_content_into(
688
destination, revision_id=revision_id)
690
def _copy_repository_tarball(self, destination, revision_id=None):
691
# get a tarball of the remote repository, and copy from that into the
693
from bzrlib import osutils
696
from StringIO import StringIO
697
# TODO: Maybe a progress bar while streaming the tarball?
698
note("Copying repository content as tarball...")
699
tar_file = self._get_tarball('bz2')
701
tar = tarfile.open('repository', fileobj=tar_file,
703
tmpdir = tempfile.mkdtemp()
705
_extract_tar(tar, tmpdir)
706
tmp_bzrdir = BzrDir.open(tmpdir)
707
tmp_repo = tmp_bzrdir.open_repository()
708
tmp_repo.copy_content_into(destination, revision_id)
710
osutils.rmtree(tmpdir)
713
# TODO: if the server doesn't support this operation, maybe do it the
714
# slow way using the _real_repository?
716
# TODO: Suggestion from john: using external tar is much faster than
717
# python's tarfile library, but it may not work on windows.
721
"""Compress the data within the repository.
723
This is not currently implemented within the smart server.
726
return self._real_repository.pack()
728
def set_make_working_trees(self, new_value):
729
raise NotImplementedError(self.set_make_working_trees)
732
def sign_revision(self, revision_id, gpg_strategy):
734
return self._real_repository.sign_revision(revision_id, gpg_strategy)
737
def get_revisions(self, revision_ids):
739
return self._real_repository.get_revisions(revision_ids)
741
def supports_rich_root(self):
743
return self._real_repository.supports_rich_root()
745
def iter_reverse_revision_history(self, revision_id):
747
return self._real_repository.iter_reverse_revision_history(revision_id)
750
def _serializer(self):
752
return self._real_repository._serializer
754
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
756
return self._real_repository.store_revision_signature(
757
gpg_strategy, plaintext, revision_id)
759
def has_signature_for_revision_id(self, revision_id):
761
return self._real_repository.has_signature_for_revision_id(revision_id)
764
class RemoteBranchLockableFiles(LockableFiles):
765
"""A 'LockableFiles' implementation that talks to a smart server.
767
This is not a public interface class.
770
def __init__(self, bzrdir, _client):
772
self._client = _client
773
self._need_find_modes = True
774
LockableFiles.__init__(
775
self, bzrdir.get_branch_transport(None),
776
'lock', lockdir.LockDir)
778
def _find_modes(self):
779
# RemoteBranches don't let the client set the mode of control files.
780
self._dir_mode = None
781
self._file_mode = None
784
"""'get' a remote path as per the LockableFiles interface.
786
:param path: the file to 'get'. If this is 'branch.conf', we do not
787
just retrieve a file, instead we ask the smart server to generate
788
a configuration for us - which is retrieved as an INI file.
790
if path == 'branch.conf':
791
path = self.bzrdir._path_for_remote_call(self._client)
792
response = self._client.call_expecting_body(
793
'Branch.get_config_file', path)
794
assert response[0][0] == 'ok', \
795
'unexpected response code %s' % (response[0],)
796
return StringIO(response[1].read_body_bytes())
799
return LockableFiles.get(self, path)
802
class RemoteBranchFormat(branch.BranchFormat):
804
def __eq__(self, other):
805
return (isinstance(other, RemoteBranchFormat) and
806
self.__dict__ == other.__dict__)
808
def get_format_description(self):
809
return 'Remote BZR Branch'
811
def get_format_string(self):
812
return 'Remote BZR Branch'
814
def open(self, a_bzrdir):
815
assert isinstance(a_bzrdir, RemoteBzrDir)
816
return a_bzrdir.open_branch()
818
def initialize(self, a_bzrdir):
819
assert isinstance(a_bzrdir, RemoteBzrDir)
820
return a_bzrdir.create_branch()
822
def supports_tags(self):
823
# Remote branches might support tags, but we won't know until we
824
# access the real remote branch.
828
class RemoteBranch(branch.Branch):
829
"""Branch stored on a server accessed by HPSS RPC.
831
At the moment most operations are mapped down to simple file operations.
834
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
836
"""Create a RemoteBranch instance.
838
:param real_branch: An optional local implementation of the branch
839
format, usually accessing the data via the VFS.
840
:param _client: Private parameter for testing.
842
# We intentionally don't call the parent class's __init__, because it
843
# will try to assign to self.tags, which is a property in this subclass.
844
# And the parent's __init__ doesn't do much anyway.
845
self._revision_history_cache = None
846
self.bzrdir = remote_bzrdir
847
if _client is not None:
848
self._client = _client
850
self._client = client._SmartClient(self.bzrdir._shared_medium)
851
self.repository = remote_repository
852
if real_branch is not None:
853
self._real_branch = real_branch
854
# Give the remote repository the matching real repo.
855
real_repo = self._real_branch.repository
856
if isinstance(real_repo, RemoteRepository):
857
real_repo._ensure_real()
858
real_repo = real_repo._real_repository
859
self.repository._set_real_repository(real_repo)
860
# Give the branch the remote repository to let fast-pathing happen.
861
self._real_branch.repository = self.repository
863
self._real_branch = None
864
# Fill out expected attributes of branch for bzrlib api users.
865
self._format = RemoteBranchFormat()
866
self.base = self.bzrdir.root_transport.base
867
self._control_files = None
868
self._lock_mode = None
869
self._lock_token = None
871
self._leave_lock = False
874
return "%s(%s)" % (self.__class__.__name__, self.base)
878
def _ensure_real(self):
879
"""Ensure that there is a _real_branch set.
881
Used before calls to self._real_branch.
883
if not self._real_branch:
884
assert vfs.vfs_enabled()
885
self.bzrdir._ensure_real()
886
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
887
# Give the remote repository the matching real repo.
888
real_repo = self._real_branch.repository
889
if isinstance(real_repo, RemoteRepository):
890
real_repo._ensure_real()
891
real_repo = real_repo._real_repository
892
self.repository._set_real_repository(real_repo)
893
# Give the branch the remote repository to let fast-pathing happen.
894
self._real_branch.repository = self.repository
895
# XXX: deal with _lock_mode == 'w'
896
if self._lock_mode == 'r':
897
self._real_branch.lock_read()
900
def control_files(self):
901
# Defer actually creating RemoteBranchLockableFiles until its needed,
902
# because it triggers an _ensure_real that we otherwise might not need.
903
if self._control_files is None:
904
self._control_files = RemoteBranchLockableFiles(
905
self.bzrdir, self._client)
906
return self._control_files
908
def _get_checkout_format(self):
910
return self._real_branch._get_checkout_format()
912
def get_physical_lock_status(self):
913
"""See Branch.get_physical_lock_status()."""
914
# should be an API call to the server, as branches must be lockable.
916
return self._real_branch.get_physical_lock_status()
919
if not self._lock_mode:
920
self._lock_mode = 'r'
922
if self._real_branch is not None:
923
self._real_branch.lock_read()
925
self._lock_count += 1
927
def _remote_lock_write(self, token):
929
branch_token = repo_token = ''
932
repo_token = self.repository.lock_write()
933
self.repository.unlock()
934
path = self.bzrdir._path_for_remote_call(self._client)
935
response = self._client.call('Branch.lock_write', path, branch_token,
937
if response[0] == 'ok':
938
ok, branch_token, repo_token = response
939
return branch_token, repo_token
940
elif response[0] == 'LockContention':
941
raise errors.LockContention('(remote lock)')
942
elif response[0] == 'TokenMismatch':
943
raise errors.TokenMismatch(token, '(remote token)')
944
elif response[0] == 'UnlockableTransport':
945
raise errors.UnlockableTransport(self.bzrdir.root_transport)
946
elif response[0] == 'ReadOnlyError':
947
raise errors.ReadOnlyError(self)
949
raise errors.UnexpectedSmartServerResponse(response)
951
def lock_write(self, token=None):
952
if not self._lock_mode:
953
remote_tokens = self._remote_lock_write(token)
954
self._lock_token, self._repo_lock_token = remote_tokens
955
assert self._lock_token, 'Remote server did not return a token!'
956
# TODO: We really, really, really don't want to call _ensure_real
957
# here, but it's the easiest way to ensure coherency between the
958
# state of the RemoteBranch and RemoteRepository objects and the
959
# physical locks. If we don't materialise the real objects here,
960
# then getting everything in the right state later is complex, so
961
# for now we just do it the lazy way.
962
# -- Andrew Bennetts, 2007-02-22.
964
if self._real_branch is not None:
965
self._real_branch.repository.lock_write(
966
token=self._repo_lock_token)
968
self._real_branch.lock_write(token=self._lock_token)
970
self._real_branch.repository.unlock()
971
if token is not None:
972
self._leave_lock = True
974
# XXX: this case seems to be unreachable; token cannot be None.
975
self._leave_lock = False
976
self._lock_mode = 'w'
978
elif self._lock_mode == 'r':
979
raise errors.ReadOnlyTransaction
981
if token is not None:
982
# A token was given to lock_write, and we're relocking, so check
983
# that the given token actually matches the one we already have.
984
if token != self._lock_token:
985
raise errors.TokenMismatch(token, self._lock_token)
986
self._lock_count += 1
987
return self._lock_token
989
def _unlock(self, branch_token, repo_token):
990
path = self.bzrdir._path_for_remote_call(self._client)
991
response = self._client.call('Branch.unlock', path, branch_token,
993
if response == ('ok',):
995
elif response[0] == 'TokenMismatch':
996
raise errors.TokenMismatch(
997
str((branch_token, repo_token)), '(remote tokens)')
999
raise errors.UnexpectedSmartServerResponse(response)
1002
self._lock_count -= 1
1003
if not self._lock_count:
1004
self._clear_cached_state()
1005
mode = self._lock_mode
1006
self._lock_mode = None
1007
if self._real_branch is not None:
1008
if not self._leave_lock:
1009
# If this RemoteBranch will remove the physical lock for the
1010
# repository, make sure the _real_branch doesn't do it
1011
# first. (Because the _real_branch's repository is set to
1012
# be the RemoteRepository.)
1013
self._real_branch.repository.leave_lock_in_place()
1014
self._real_branch.unlock()
1016
# Only write-locked branched need to make a remote method call
1017
# to perfom the unlock.
1019
assert self._lock_token, 'Locked, but no token!'
1020
branch_token = self._lock_token
1021
repo_token = self._repo_lock_token
1022
self._lock_token = None
1023
self._repo_lock_token = None
1024
if not self._leave_lock:
1025
self._unlock(branch_token, repo_token)
1027
def break_lock(self):
1029
return self._real_branch.break_lock()
1031
def leave_lock_in_place(self):
1032
self._leave_lock = True
1034
def dont_leave_lock_in_place(self):
1035
self._leave_lock = False
1037
def last_revision_info(self):
1038
"""See Branch.last_revision_info()."""
1039
path = self.bzrdir._path_for_remote_call(self._client)
1040
response = self._client.call('Branch.last_revision_info', path)
1041
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1042
revno = int(response[1])
1043
last_revision = response[2]
1044
return (revno, last_revision)
1046
def _gen_revision_history(self):
1047
"""See Branch._gen_revision_history()."""
1048
path = self.bzrdir._path_for_remote_call(self._client)
1049
response = self._client.call_expecting_body(
1050
'Branch.revision_history', path)
1051
assert response[0][0] == 'ok', ('unexpected response code %s'
1053
result = response[1].read_body_bytes().split('\x00')
1059
def set_revision_history(self, rev_history):
1060
# Send just the tip revision of the history; the server will generate
1061
# the full history from that. If the revision doesn't exist in this
1062
# branch, NoSuchRevision will be raised.
1063
path = self.bzrdir._path_for_remote_call(self._client)
1064
if rev_history == []:
1067
rev_id = rev_history[-1]
1068
self._clear_cached_state()
1069
response = self._client.call('Branch.set_last_revision',
1070
path, self._lock_token, self._repo_lock_token, rev_id)
1071
if response[0] == 'NoSuchRevision':
1072
raise NoSuchRevision(self, rev_id)
1074
assert response == ('ok',), (
1075
'unexpected response code %r' % (response,))
1076
self._cache_revision_history(rev_history)
1078
def get_parent(self):
1080
return self._real_branch.get_parent()
1082
def set_parent(self, url):
1084
return self._real_branch.set_parent(url)
1086
def get_config(self):
1087
return RemoteBranchConfig(self)
1089
def sprout(self, to_bzrdir, revision_id=None):
1090
# Like Branch.sprout, except that it sprouts a branch in the default
1091
# format, because RemoteBranches can't be created at arbitrary URLs.
1092
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1093
# to_bzrdir.create_branch...
1094
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1095
self.copy_content_into(result, revision_id=revision_id)
1096
result.set_parent(self.bzrdir.root_transport.base)
1100
def pull(self, source, overwrite=False, stop_revision=None,
1102
# FIXME: This asks the real branch to run the hooks, which means
1103
# they're called with the wrong target branch parameter.
1104
# The test suite specifically allows this at present but it should be
1105
# fixed. It should get a _override_hook_target branch,
1106
# as push does. -- mbp 20070405
1108
self._real_branch.pull(
1109
source, overwrite=overwrite, stop_revision=stop_revision,
1113
def push(self, target, overwrite=False, stop_revision=None):
1115
return self._real_branch.push(
1116
target, overwrite=overwrite, stop_revision=stop_revision,
1117
_override_hook_source_branch=self)
1119
def is_locked(self):
1120
return self._lock_count >= 1
1122
def set_last_revision_info(self, revno, revision_id):
1124
self._clear_cached_state()
1125
return self._real_branch.set_last_revision_info(revno, revision_id)
1127
def generate_revision_history(self, revision_id, last_rev=None,
1130
return self._real_branch.generate_revision_history(
1131
revision_id, last_rev=last_rev, other_branch=other_branch)
1136
return self._real_branch.tags
1138
def set_push_location(self, location):
1140
return self._real_branch.set_push_location(location)
1142
def update_revisions(self, other, stop_revision=None):
1144
return self._real_branch.update_revisions(
1145
other, stop_revision=stop_revision)
1148
class RemoteBranchConfig(BranchConfig):
1151
self.branch._ensure_real()
1152
return self.branch._real_branch.get_config().username()
1154
def _get_branch_data_config(self):
1155
self.branch._ensure_real()
1156
if self._branch_data_config is None:
1157
self._branch_data_config = TreeConfig(self.branch._real_branch)
1158
return self._branch_data_config
1161
def _extract_tar(tar, to_dir):
1162
"""Extract all the contents of a tarfile object.
1164
A replacement for extractall, which is not present in python2.4
1167
tar.extract(tarinfo, to_dir)