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.pack import ContainerReader
35
from bzrlib.revision import NULL_REVISION
36
from bzrlib.smart import client, vfs
37
from bzrlib.symbol_versioning import (
41
from bzrlib.trace import note
43
# Note: RemoteBzrDirFormat is in bzrdir.py
45
class RemoteBzrDir(BzrDir):
46
"""Control directory on a remote server, accessed via bzr:// or similar."""
48
def __init__(self, transport, _client=None):
49
"""Construct a RemoteBzrDir.
51
:param _client: Private parameter for testing. Disables probing and the
54
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
55
# this object holds a delegated bzrdir that uses file-level operations
56
# to talk to the other side
57
self._real_bzrdir = None
60
self._shared_medium = transport.get_shared_medium()
61
self._client = client._SmartClient(self._shared_medium)
63
self._client = _client
64
self._shared_medium = None
67
path = self._path_for_remote_call(self._client)
68
response = self._client.call('BzrDir.open', path)
69
if response not in [('yes',), ('no',)]:
70
raise errors.UnexpectedSmartServerResponse(response)
71
if response == ('no',):
72
raise errors.NotBranchError(path=transport.base)
74
def _ensure_real(self):
75
"""Ensure that there is a _real_bzrdir set.
77
Used before calls to self._real_bzrdir.
79
if not self._real_bzrdir:
80
self._real_bzrdir = BzrDir.open_from_transport(
81
self.root_transport, _server_formats=False)
83
def create_repository(self, shared=False):
85
self._real_bzrdir.create_repository(shared=shared)
86
return self.open_repository()
88
def create_branch(self):
90
real_branch = self._real_bzrdir.create_branch()
91
return RemoteBranch(self, self.find_repository(), real_branch)
93
def destroy_branch(self):
94
"""See BzrDir.destroy_branch"""
96
self._real_bzrdir.destroy_branch()
98
def create_workingtree(self, revision_id=None):
99
raise errors.NotLocalUrl(self.transport.base)
101
def find_branch_format(self):
102
"""Find the branch 'format' for this bzrdir.
104
This might be a synthetic object for e.g. RemoteBranch and SVN.
106
b = self.open_branch()
109
def get_branch_reference(self):
110
"""See BzrDir.get_branch_reference()."""
111
path = self._path_for_remote_call(self._client)
112
response = self._client.call('BzrDir.open_branch', path)
113
if response[0] == 'ok':
114
if response[1] == '':
115
# branch at this location.
118
# a branch reference, use the existing BranchReference logic.
120
elif response == ('nobranch',):
121
raise errors.NotBranchError(path=self.root_transport.base)
123
raise errors.UnexpectedSmartServerResponse(response)
125
def open_branch(self, _unsupported=False):
126
assert _unsupported == False, 'unsupported flag support not implemented yet.'
127
reference_url = self.get_branch_reference()
128
if reference_url is None:
129
# branch at this location.
130
return RemoteBranch(self, self.find_repository())
132
# a branch reference, use the existing BranchReference logic.
133
format = BranchReferenceFormat()
134
return format.open(self, _found=True, location=reference_url)
136
def open_repository(self):
137
path = self._path_for_remote_call(self._client)
138
response = self._client.call('BzrDir.find_repository', path)
139
assert response[0] in ('ok', 'norepository'), \
140
'unexpected response code %s' % (response,)
141
if response[0] == 'norepository':
142
raise errors.NoRepositoryPresent(self)
143
assert len(response) == 4, 'incorrect response length %s' % (response,)
144
if response[1] == '':
145
format = RemoteRepositoryFormat()
146
format.rich_root_data = (response[2] == 'yes')
147
format.supports_tree_reference = (response[3] == 'yes')
148
return RemoteRepository(self, format)
150
raise errors.NoRepositoryPresent(self)
152
def open_workingtree(self, recommend_upgrade=True):
154
if self._real_bzrdir.has_workingtree():
155
raise errors.NotLocalUrl(self.root_transport)
157
raise errors.NoWorkingTree(self.root_transport.base)
159
def _path_for_remote_call(self, client):
160
"""Return the path to be used for this bzrdir in a remote call."""
161
return client.remote_path_from_transport(self.root_transport)
163
def get_branch_transport(self, branch_format):
165
return self._real_bzrdir.get_branch_transport(branch_format)
167
def get_repository_transport(self, repository_format):
169
return self._real_bzrdir.get_repository_transport(repository_format)
171
def get_workingtree_transport(self, workingtree_format):
173
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
175
def can_convert_format(self):
176
"""Upgrading of remote bzrdirs is not supported yet."""
179
def needs_format_conversion(self, format=None):
180
"""Upgrading of remote bzrdirs is not supported yet."""
183
def clone(self, url, revision_id=None, force_new_repo=False):
185
return self._real_bzrdir.clone(url, revision_id=revision_id,
186
force_new_repo=force_new_repo)
189
class RemoteRepositoryFormat(repository.RepositoryFormat):
190
"""Format for repositories accessed over a _SmartClient.
192
Instances of this repository are represented by RemoteRepository
195
The RemoteRepositoryFormat is parameterised during construction
196
to reflect the capabilities of the real, remote format. Specifically
197
the attributes rich_root_data and supports_tree_reference are set
198
on a per instance basis, and are not set (and should not be) at
202
_matchingbzrdir = RemoteBzrDirFormat
204
def initialize(self, a_bzrdir, shared=False):
205
assert isinstance(a_bzrdir, RemoteBzrDir), \
206
'%r is not a RemoteBzrDir' % (a_bzrdir,)
207
return a_bzrdir.create_repository(shared=shared)
209
def open(self, a_bzrdir):
210
assert isinstance(a_bzrdir, RemoteBzrDir)
211
return a_bzrdir.open_repository()
213
def get_format_description(self):
214
return 'bzr remote repository'
216
def __eq__(self, other):
217
return self.__class__ == other.__class__
219
def check_conversion_target(self, target_format):
220
if self.rich_root_data and not target_format.rich_root_data:
221
raise errors.BadConversionTarget(
222
'Does not support rich root data.', target_format)
223
if (self.supports_tree_reference and
224
not getattr(target_format, 'supports_tree_reference', False)):
225
raise errors.BadConversionTarget(
226
'Does not support nested trees', target_format)
229
class RemoteRepository(object):
230
"""Repository accessed over rpc.
232
For the moment most operations are performed using local transport-backed
236
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
237
"""Create a RemoteRepository instance.
239
:param remote_bzrdir: The bzrdir hosting this repository.
240
:param format: The RemoteFormat object to use.
241
:param real_repository: If not None, a local implementation of the
242
repository logic for the repository, usually accessing the data
244
:param _client: Private testing parameter - override the smart client
245
to be used by the repository.
248
self._real_repository = real_repository
250
self._real_repository = None
251
self.bzrdir = remote_bzrdir
253
self._client = client._SmartClient(self.bzrdir._shared_medium)
255
self._client = _client
256
self._format = format
257
self._lock_mode = None
258
self._lock_token = None
260
self._leave_lock = False
262
self._reconcile_does_inventory_gc = True
263
self._reconcile_fixes_text_parents = True
264
self.base = self.bzrdir.transport.base
267
return "%s(%s)" % (self.__class__.__name__, self.base)
271
def abort_write_group(self):
272
"""Complete a write group on the decorated repository.
274
Smart methods peform operations in a single step so this api
275
is not really applicable except as a compatibility thunk
276
for older plugins that don't use e.g. the CommitBuilder
280
return self._real_repository.abort_write_group()
282
def commit_write_group(self):
283
"""Complete a write group on the decorated repository.
285
Smart methods peform operations in a single step so this api
286
is not really applicable except as a compatibility thunk
287
for older plugins that don't use e.g. the CommitBuilder
291
return self._real_repository.commit_write_group()
293
def _ensure_real(self):
294
"""Ensure that there is a _real_repository set.
296
Used before calls to self._real_repository.
298
if not self._real_repository:
299
self.bzrdir._ensure_real()
300
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
301
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
303
def get_revision_graph(self, revision_id=None):
304
"""See Repository.get_revision_graph()."""
305
if revision_id is None:
307
elif revision_id == NULL_REVISION:
310
path = self.bzrdir._path_for_remote_call(self._client)
311
assert type(revision_id) is str
312
response = self._client.call_expecting_body(
313
'Repository.get_revision_graph', path, revision_id)
314
if response[0][0] not in ['ok', 'nosuchrevision']:
315
raise errors.UnexpectedSmartServerResponse(response[0])
316
if response[0][0] == 'ok':
317
coded = response[1].read_body_bytes()
319
# no revisions in this repository!
321
lines = coded.split('\n')
324
d = tuple(line.split())
325
revision_graph[d[0]] = d[1:]
327
return revision_graph
329
response_body = response[1].read_body_bytes()
330
assert response_body == ''
331
raise NoSuchRevision(self, revision_id)
333
def has_revision(self, revision_id):
334
"""See Repository.has_revision()."""
335
if revision_id is None:
336
# The null revision is always present.
338
path = self.bzrdir._path_for_remote_call(self._client)
339
response = self._client.call('Repository.has_revision', path, revision_id)
340
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
341
return response[0] == 'yes'
343
def has_same_location(self, other):
344
return (self.__class__ == other.__class__ and
345
self.bzrdir.transport.base == other.bzrdir.transport.base)
347
def get_graph(self, other_repository=None):
348
"""Return the graph for this repository format"""
350
return self._real_repository.get_graph(other_repository)
352
def gather_stats(self, revid=None, committers=None):
353
"""See Repository.gather_stats()."""
354
path = self.bzrdir._path_for_remote_call(self._client)
355
if revid in (None, NULL_REVISION):
359
if committers is None or not committers:
360
fmt_committers = 'no'
362
fmt_committers = 'yes'
363
response = self._client.call_expecting_body(
364
'Repository.gather_stats', path, fmt_revid, fmt_committers)
365
assert response[0][0] == 'ok', \
366
'unexpected response code %s' % (response[0],)
368
body = response[1].read_body_bytes()
370
for line in body.split('\n'):
373
key, val_text = line.split(':')
374
if key in ('revisions', 'size', 'committers'):
375
result[key] = int(val_text)
376
elif key in ('firstrev', 'latestrev'):
377
values = val_text.split(' ')[1:]
378
result[key] = (float(values[0]), long(values[1]))
382
def get_physical_lock_status(self):
383
"""See Repository.get_physical_lock_status()."""
386
def is_in_write_group(self):
387
"""Return True if there is an open write group.
389
write groups are only applicable locally for the smart server..
391
if self._real_repository:
392
return self._real_repository.is_in_write_group()
395
return self._lock_count >= 1
398
"""See Repository.is_shared()."""
399
path = self.bzrdir._path_for_remote_call(self._client)
400
response = self._client.call('Repository.is_shared', path)
401
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
402
return response[0] == 'yes'
405
# wrong eventually - want a local lock cache context
406
if not self._lock_mode:
407
self._lock_mode = 'r'
409
if self._real_repository is not None:
410
self._real_repository.lock_read()
412
self._lock_count += 1
414
def _remote_lock_write(self, token):
415
path = self.bzrdir._path_for_remote_call(self._client)
418
response = self._client.call('Repository.lock_write', path, token)
419
if response[0] == 'ok':
422
elif response[0] == 'LockContention':
423
raise errors.LockContention('(remote lock)')
424
elif response[0] == 'UnlockableTransport':
425
raise errors.UnlockableTransport(self.bzrdir.root_transport)
426
elif response[0] == 'LockFailed':
427
raise errors.LockFailed(response[1], response[2])
429
raise errors.UnexpectedSmartServerResponse(response)
431
def lock_write(self, token=None):
432
if not self._lock_mode:
433
self._lock_token = self._remote_lock_write(token)
434
assert self._lock_token, 'Remote server did not return a token!'
435
if self._real_repository is not None:
436
self._real_repository.lock_write(token=self._lock_token)
437
if token is not None:
438
self._leave_lock = True
440
self._leave_lock = False
441
self._lock_mode = 'w'
443
elif self._lock_mode == 'r':
444
raise errors.ReadOnlyError(self)
446
self._lock_count += 1
447
return self._lock_token
449
def leave_lock_in_place(self):
450
self._leave_lock = True
452
def dont_leave_lock_in_place(self):
453
self._leave_lock = False
455
def _set_real_repository(self, repository):
456
"""Set the _real_repository for this repository.
458
:param repository: The repository to fallback to for non-hpss
459
implemented operations.
461
assert not isinstance(repository, RemoteRepository)
462
self._real_repository = repository
463
if self._lock_mode == 'w':
464
# if we are already locked, the real repository must be able to
465
# acquire the lock with our token.
466
self._real_repository.lock_write(self._lock_token)
467
elif self._lock_mode == 'r':
468
self._real_repository.lock_read()
470
def start_write_group(self):
471
"""Start a write group on the decorated repository.
473
Smart methods peform operations in a single step so this api
474
is not really applicable except as a compatibility thunk
475
for older plugins that don't use e.g. the CommitBuilder
479
return self._real_repository.start_write_group()
481
def _unlock(self, token):
482
path = self.bzrdir._path_for_remote_call(self._client)
483
response = self._client.call('Repository.unlock', path, token)
484
if response == ('ok',):
486
elif response[0] == 'TokenMismatch':
487
raise errors.TokenMismatch(token, '(remote token)')
489
raise errors.UnexpectedSmartServerResponse(response)
492
if self._lock_count == 1 and self._lock_mode == 'w':
493
# don't unlock if inside a write group.
494
if self.is_in_write_group():
495
raise errors.BzrError(
496
'Must end write groups before releasing write locks.')
497
self._lock_count -= 1
498
if not self._lock_count:
499
mode = self._lock_mode
500
self._lock_mode = None
501
if self._real_repository is not None:
502
self._real_repository.unlock()
504
# Only write-locked repositories need to make a remote method
505
# call to perfom the unlock.
507
assert self._lock_token, 'Locked, but no token!'
508
token = self._lock_token
509
self._lock_token = None
510
if not self._leave_lock:
513
def break_lock(self):
514
# should hand off to the network
516
return self._real_repository.break_lock()
518
def _get_tarball(self, compression):
519
"""Return a TemporaryFile containing a repository tarball.
521
Returns None if the server does not support sending tarballs.
524
path = self.bzrdir._path_for_remote_call(self._client)
525
response, protocol = self._client.call_expecting_body(
526
'Repository.tarball', path, compression)
527
if response[0] == 'ok':
528
# Extract the tarball and return it
529
t = tempfile.NamedTemporaryFile()
530
# TODO: rpc layer should read directly into it...
531
t.write(protocol.read_body_bytes())
534
if (response == ('error', "Generic bzr smart protocol error: "
535
"bad request 'Repository.tarball'") or
536
response == ('error', "Generic bzr smart protocol error: "
537
"bad request u'Repository.tarball'")):
538
protocol.cancel_read_body()
540
raise errors.UnexpectedSmartServerResponse(response)
542
def sprout(self, to_bzrdir, revision_id=None):
543
# TODO: Option to control what format is created?
544
dest_repo = to_bzrdir.create_repository()
545
dest_repo.fetch(self, revision_id=revision_id)
548
### These methods are just thin shims to the VFS object for now.
550
def revision_tree(self, revision_id):
552
return self._real_repository.revision_tree(revision_id)
554
def get_serializer_format(self):
556
return self._real_repository.get_serializer_format()
558
def get_commit_builder(self, branch, parents, config, timestamp=None,
559
timezone=None, committer=None, revprops=None,
561
# FIXME: It ought to be possible to call this without immediately
562
# triggering _ensure_real. For now it's the easiest thing to do.
564
builder = self._real_repository.get_commit_builder(branch, parents,
565
config, timestamp=timestamp, timezone=timezone,
566
committer=committer, revprops=revprops, revision_id=revision_id)
567
# Make the builder use this RemoteRepository rather than the real one.
568
builder.repository = self
572
def add_inventory(self, revid, inv, parents):
574
return self._real_repository.add_inventory(revid, inv, parents)
577
def add_revision(self, rev_id, rev, inv=None, config=None):
579
return self._real_repository.add_revision(
580
rev_id, rev, inv=inv, config=config)
583
def get_inventory(self, revision_id):
585
return self._real_repository.get_inventory(revision_id)
588
def get_revision(self, revision_id):
590
return self._real_repository.get_revision(revision_id)
593
def weave_store(self):
595
return self._real_repository.weave_store
597
def get_transaction(self):
599
return self._real_repository.get_transaction()
602
def clone(self, a_bzrdir, revision_id=None):
604
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
606
def make_working_trees(self):
607
"""RemoteRepositories never create working trees by default."""
610
def fetch(self, source, revision_id=None, pb=None):
611
if self.has_same_location(source):
612
# check that last_revision is in 'from' and then return a
614
if (revision_id is not None and
615
not _mod_revision.is_null(revision_id)):
616
self.get_revision(revision_id)
619
return self._real_repository.fetch(
620
source, revision_id=revision_id, pb=pb)
622
def create_bundle(self, target, base, fileobj, format=None):
624
self._real_repository.create_bundle(target, base, fileobj, format)
627
def control_weaves(self):
629
return self._real_repository.control_weaves
632
def get_ancestry(self, revision_id, topo_sorted=True):
634
return self._real_repository.get_ancestry(revision_id, topo_sorted)
637
def get_inventory_weave(self):
639
return self._real_repository.get_inventory_weave()
641
def fileids_altered_by_revision_ids(self, revision_ids):
643
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
645
def get_versioned_file_checker(self, revisions, revision_versions_cache):
647
return self._real_repository.get_versioned_file_checker(
648
revisions, revision_versions_cache)
650
def iter_files_bytes(self, desired_files):
651
"""See Repository.iter_file_bytes.
654
return self._real_repository.iter_files_bytes(desired_files)
657
def get_signature_text(self, revision_id):
659
return self._real_repository.get_signature_text(revision_id)
662
def get_revision_graph_with_ghosts(self, revision_ids=None):
664
return self._real_repository.get_revision_graph_with_ghosts(
665
revision_ids=revision_ids)
668
def get_inventory_xml(self, revision_id):
670
return self._real_repository.get_inventory_xml(revision_id)
672
def deserialise_inventory(self, revision_id, xml):
674
return self._real_repository.deserialise_inventory(revision_id, xml)
676
def reconcile(self, other=None, thorough=False):
678
return self._real_repository.reconcile(other=other, thorough=thorough)
680
def all_revision_ids(self):
682
return self._real_repository.all_revision_ids()
685
def get_deltas_for_revisions(self, revisions):
687
return self._real_repository.get_deltas_for_revisions(revisions)
690
def get_revision_delta(self, revision_id):
692
return self._real_repository.get_revision_delta(revision_id)
695
def revision_trees(self, revision_ids):
697
return self._real_repository.revision_trees(revision_ids)
700
def get_revision_reconcile(self, revision_id):
702
return self._real_repository.get_revision_reconcile(revision_id)
705
def check(self, revision_ids=None):
707
return self._real_repository.check(revision_ids=revision_ids)
709
def copy_content_into(self, destination, revision_id=None):
711
return self._real_repository.copy_content_into(
712
destination, revision_id=revision_id)
714
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
715
# get a tarball of the remote repository, and copy from that into the
717
from bzrlib import osutils
720
from StringIO import StringIO
721
# TODO: Maybe a progress bar while streaming the tarball?
722
note("Copying repository content as tarball...")
723
tar_file = self._get_tarball('bz2')
726
destination = to_bzrdir.create_repository()
728
tar = tarfile.open('repository', fileobj=tar_file,
730
tmpdir = tempfile.mkdtemp()
732
_extract_tar(tar, tmpdir)
733
tmp_bzrdir = BzrDir.open(tmpdir)
734
tmp_repo = tmp_bzrdir.open_repository()
735
tmp_repo.copy_content_into(destination, revision_id)
737
osutils.rmtree(tmpdir)
741
# TODO: Suggestion from john: using external tar is much faster than
742
# python's tarfile library, but it may not work on windows.
746
"""Compress the data within the repository.
748
This is not currently implemented within the smart server.
751
return self._real_repository.pack()
753
def set_make_working_trees(self, new_value):
754
raise NotImplementedError(self.set_make_working_trees)
757
def sign_revision(self, revision_id, gpg_strategy):
759
return self._real_repository.sign_revision(revision_id, gpg_strategy)
762
def get_revisions(self, revision_ids):
764
return self._real_repository.get_revisions(revision_ids)
766
def supports_rich_root(self):
768
return self._real_repository.supports_rich_root()
770
def iter_reverse_revision_history(self, revision_id):
772
return self._real_repository.iter_reverse_revision_history(revision_id)
775
def _serializer(self):
777
return self._real_repository._serializer
779
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
781
return self._real_repository.store_revision_signature(
782
gpg_strategy, plaintext, revision_id)
784
def has_signature_for_revision_id(self, revision_id):
786
return self._real_repository.has_signature_for_revision_id(revision_id)
788
def get_data_stream(self, revision_ids):
789
path = self.bzrdir._path_for_remote_call(self._client)
790
response, protocol = self._client.call_expecting_body(
791
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
792
if response == ('ok',):
793
return self._deserialise_stream(protocol)
794
elif (response == ('error', "Generic bzr smart protocol error: "
795
"bad request 'Repository.stream_knit_data_for_revisions'") or
796
response == ('error', "Generic bzr smart protocol error: "
797
"bad request u'Repository.stream_knit_data_for_revisions'")):
798
protocol.cancel_read_body()
800
return self._real_repository.get_data_stream(revision_ids)
802
raise errors.UnexpectedSmartServerResponse(response)
804
def _deserialise_stream(self, protocol):
805
buffer = StringIO(protocol.read_body_bytes())
806
reader = ContainerReader(buffer)
807
for record_names, read_bytes in reader.iter_records():
809
# These records should have only one name, and that name
810
# should be a one-element tuple.
811
[name_tuple] = record_names
813
raise errors.SmartProtocolError(
814
'Repository data stream had invalid record name %r'
816
yield name_tuple, read_bytes(None)
818
def insert_data_stream(self, stream):
820
self._real_repository.insert_data_stream(stream)
822
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
824
return self._real_repository.item_keys_introduced_by(revision_ids,
827
def revision_graph_can_have_wrong_parents(self):
828
# The answer depends on the remote repo format.
830
return self._real_repository.revision_graph_can_have_wrong_parents()
832
def _find_inconsistent_revision_parents(self):
834
return self._real_repository._find_inconsistent_revision_parents()
836
def _check_for_inconsistent_revision_parents(self):
838
return self._real_repository._check_for_inconsistent_revision_parents()
841
class RemoteBranchLockableFiles(LockableFiles):
842
"""A 'LockableFiles' implementation that talks to a smart server.
844
This is not a public interface class.
847
def __init__(self, bzrdir, _client):
849
self._client = _client
850
self._need_find_modes = True
851
LockableFiles.__init__(
852
self, bzrdir.get_branch_transport(None),
853
'lock', lockdir.LockDir)
855
def _find_modes(self):
856
# RemoteBranches don't let the client set the mode of control files.
857
self._dir_mode = None
858
self._file_mode = None
861
"""'get' a remote path as per the LockableFiles interface.
863
:param path: the file to 'get'. If this is 'branch.conf', we do not
864
just retrieve a file, instead we ask the smart server to generate
865
a configuration for us - which is retrieved as an INI file.
867
if path == 'branch.conf':
868
path = self.bzrdir._path_for_remote_call(self._client)
869
response = self._client.call_expecting_body(
870
'Branch.get_config_file', path)
871
assert response[0][0] == 'ok', \
872
'unexpected response code %s' % (response[0],)
873
return StringIO(response[1].read_body_bytes())
876
return LockableFiles.get(self, path)
879
class RemoteBranchFormat(branch.BranchFormat):
881
def __eq__(self, other):
882
return (isinstance(other, RemoteBranchFormat) and
883
self.__dict__ == other.__dict__)
885
def get_format_description(self):
886
return 'Remote BZR Branch'
888
def get_format_string(self):
889
return 'Remote BZR Branch'
891
def open(self, a_bzrdir):
892
assert isinstance(a_bzrdir, RemoteBzrDir)
893
return a_bzrdir.open_branch()
895
def initialize(self, a_bzrdir):
896
assert isinstance(a_bzrdir, RemoteBzrDir)
897
return a_bzrdir.create_branch()
899
def supports_tags(self):
900
# Remote branches might support tags, but we won't know until we
901
# access the real remote branch.
905
class RemoteBranch(branch.Branch):
906
"""Branch stored on a server accessed by HPSS RPC.
908
At the moment most operations are mapped down to simple file operations.
911
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
913
"""Create a RemoteBranch instance.
915
:param real_branch: An optional local implementation of the branch
916
format, usually accessing the data via the VFS.
917
:param _client: Private parameter for testing.
919
# We intentionally don't call the parent class's __init__, because it
920
# will try to assign to self.tags, which is a property in this subclass.
921
# And the parent's __init__ doesn't do much anyway.
922
self._revision_history_cache = None
923
self.bzrdir = remote_bzrdir
924
if _client is not None:
925
self._client = _client
927
self._client = client._SmartClient(self.bzrdir._shared_medium)
928
self.repository = remote_repository
929
if real_branch is not None:
930
self._real_branch = real_branch
931
# Give the remote repository the matching real repo.
932
real_repo = self._real_branch.repository
933
if isinstance(real_repo, RemoteRepository):
934
real_repo._ensure_real()
935
real_repo = real_repo._real_repository
936
self.repository._set_real_repository(real_repo)
937
# Give the branch the remote repository to let fast-pathing happen.
938
self._real_branch.repository = self.repository
940
self._real_branch = None
941
# Fill out expected attributes of branch for bzrlib api users.
942
self._format = RemoteBranchFormat()
943
self.base = self.bzrdir.root_transport.base
944
self._control_files = None
945
self._lock_mode = None
946
self._lock_token = None
948
self._leave_lock = False
951
return "%s(%s)" % (self.__class__.__name__, self.base)
955
def _ensure_real(self):
956
"""Ensure that there is a _real_branch set.
958
Used before calls to self._real_branch.
960
if not self._real_branch:
961
assert vfs.vfs_enabled()
962
self.bzrdir._ensure_real()
963
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
964
# Give the remote repository the matching real repo.
965
real_repo = self._real_branch.repository
966
if isinstance(real_repo, RemoteRepository):
967
real_repo._ensure_real()
968
real_repo = real_repo._real_repository
969
self.repository._set_real_repository(real_repo)
970
# Give the branch the remote repository to let fast-pathing happen.
971
self._real_branch.repository = self.repository
972
# XXX: deal with _lock_mode == 'w'
973
if self._lock_mode == 'r':
974
self._real_branch.lock_read()
977
def control_files(self):
978
# Defer actually creating RemoteBranchLockableFiles until its needed,
979
# because it triggers an _ensure_real that we otherwise might not need.
980
if self._control_files is None:
981
self._control_files = RemoteBranchLockableFiles(
982
self.bzrdir, self._client)
983
return self._control_files
985
def _get_checkout_format(self):
987
return self._real_branch._get_checkout_format()
989
def get_physical_lock_status(self):
990
"""See Branch.get_physical_lock_status()."""
991
# should be an API call to the server, as branches must be lockable.
993
return self._real_branch.get_physical_lock_status()
996
if not self._lock_mode:
997
self._lock_mode = 'r'
999
if self._real_branch is not None:
1000
self._real_branch.lock_read()
1002
self._lock_count += 1
1004
def _remote_lock_write(self, token):
1006
branch_token = repo_token = ''
1008
branch_token = token
1009
repo_token = self.repository.lock_write()
1010
self.repository.unlock()
1011
path = self.bzrdir._path_for_remote_call(self._client)
1012
response = self._client.call('Branch.lock_write', path, branch_token,
1014
if response[0] == 'ok':
1015
ok, branch_token, repo_token = response
1016
return branch_token, repo_token
1017
elif response[0] == 'LockContention':
1018
raise errors.LockContention('(remote lock)')
1019
elif response[0] == 'TokenMismatch':
1020
raise errors.TokenMismatch(token, '(remote token)')
1021
elif response[0] == 'UnlockableTransport':
1022
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1023
elif response[0] == 'ReadOnlyError':
1024
raise errors.ReadOnlyError(self)
1025
elif response[0] == 'LockFailed':
1026
raise errors.LockFailed(response[1], response[2])
1028
raise errors.UnexpectedSmartServerResponse(response)
1030
def lock_write(self, token=None):
1031
if not self._lock_mode:
1032
remote_tokens = self._remote_lock_write(token)
1033
self._lock_token, self._repo_lock_token = remote_tokens
1034
assert self._lock_token, 'Remote server did not return a token!'
1035
# TODO: We really, really, really don't want to call _ensure_real
1036
# here, but it's the easiest way to ensure coherency between the
1037
# state of the RemoteBranch and RemoteRepository objects and the
1038
# physical locks. If we don't materialise the real objects here,
1039
# then getting everything in the right state later is complex, so
1040
# for now we just do it the lazy way.
1041
# -- Andrew Bennetts, 2007-02-22.
1043
if self._real_branch is not None:
1044
self._real_branch.repository.lock_write(
1045
token=self._repo_lock_token)
1047
self._real_branch.lock_write(token=self._lock_token)
1049
self._real_branch.repository.unlock()
1050
if token is not None:
1051
self._leave_lock = True
1053
# XXX: this case seems to be unreachable; token cannot be None.
1054
self._leave_lock = False
1055
self._lock_mode = 'w'
1056
self._lock_count = 1
1057
elif self._lock_mode == 'r':
1058
raise errors.ReadOnlyTransaction
1060
if token is not None:
1061
# A token was given to lock_write, and we're relocking, so check
1062
# that the given token actually matches the one we already have.
1063
if token != self._lock_token:
1064
raise errors.TokenMismatch(token, self._lock_token)
1065
self._lock_count += 1
1066
return self._lock_token
1068
def _unlock(self, branch_token, repo_token):
1069
path = self.bzrdir._path_for_remote_call(self._client)
1070
response = self._client.call('Branch.unlock', path, branch_token,
1072
if response == ('ok',):
1074
elif response[0] == 'TokenMismatch':
1075
raise errors.TokenMismatch(
1076
str((branch_token, repo_token)), '(remote tokens)')
1078
raise errors.UnexpectedSmartServerResponse(response)
1081
self._lock_count -= 1
1082
if not self._lock_count:
1083
self._clear_cached_state()
1084
mode = self._lock_mode
1085
self._lock_mode = None
1086
if self._real_branch is not None:
1087
if not self._leave_lock:
1088
# If this RemoteBranch will remove the physical lock for the
1089
# repository, make sure the _real_branch doesn't do it
1090
# first. (Because the _real_branch's repository is set to
1091
# be the RemoteRepository.)
1092
self._real_branch.repository.leave_lock_in_place()
1093
self._real_branch.unlock()
1095
# Only write-locked branched need to make a remote method call
1096
# to perfom the unlock.
1098
assert self._lock_token, 'Locked, but no token!'
1099
branch_token = self._lock_token
1100
repo_token = self._repo_lock_token
1101
self._lock_token = None
1102
self._repo_lock_token = None
1103
if not self._leave_lock:
1104
self._unlock(branch_token, repo_token)
1106
def break_lock(self):
1108
return self._real_branch.break_lock()
1110
def leave_lock_in_place(self):
1111
self._leave_lock = True
1113
def dont_leave_lock_in_place(self):
1114
self._leave_lock = False
1116
def last_revision_info(self):
1117
"""See Branch.last_revision_info()."""
1118
path = self.bzrdir._path_for_remote_call(self._client)
1119
response = self._client.call('Branch.last_revision_info', path)
1120
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1121
revno = int(response[1])
1122
last_revision = response[2]
1123
return (revno, last_revision)
1125
def _gen_revision_history(self):
1126
"""See Branch._gen_revision_history()."""
1127
path = self.bzrdir._path_for_remote_call(self._client)
1128
response = self._client.call_expecting_body(
1129
'Branch.revision_history', path)
1130
assert response[0][0] == 'ok', ('unexpected response code %s'
1132
result = response[1].read_body_bytes().split('\x00')
1138
def set_revision_history(self, rev_history):
1139
# Send just the tip revision of the history; the server will generate
1140
# the full history from that. If the revision doesn't exist in this
1141
# branch, NoSuchRevision will be raised.
1142
path = self.bzrdir._path_for_remote_call(self._client)
1143
if rev_history == []:
1146
rev_id = rev_history[-1]
1147
self._clear_cached_state()
1148
response = self._client.call('Branch.set_last_revision',
1149
path, self._lock_token, self._repo_lock_token, rev_id)
1150
if response[0] == 'NoSuchRevision':
1151
raise NoSuchRevision(self, rev_id)
1153
assert response == ('ok',), (
1154
'unexpected response code %r' % (response,))
1155
self._cache_revision_history(rev_history)
1157
def get_parent(self):
1159
return self._real_branch.get_parent()
1161
def set_parent(self, url):
1163
return self._real_branch.set_parent(url)
1165
def get_config(self):
1166
return RemoteBranchConfig(self)
1168
def sprout(self, to_bzrdir, revision_id=None):
1169
# Like Branch.sprout, except that it sprouts a branch in the default
1170
# format, because RemoteBranches can't be created at arbitrary URLs.
1171
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1172
# to_bzrdir.create_branch...
1173
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1174
self.copy_content_into(result, revision_id=revision_id)
1175
result.set_parent(self.bzrdir.root_transport.base)
1179
def pull(self, source, overwrite=False, stop_revision=None,
1181
# FIXME: This asks the real branch to run the hooks, which means
1182
# they're called with the wrong target branch parameter.
1183
# The test suite specifically allows this at present but it should be
1184
# fixed. It should get a _override_hook_target branch,
1185
# as push does. -- mbp 20070405
1187
self._real_branch.pull(
1188
source, overwrite=overwrite, stop_revision=stop_revision,
1192
def push(self, target, overwrite=False, stop_revision=None):
1194
return self._real_branch.push(
1195
target, overwrite=overwrite, stop_revision=stop_revision,
1196
_override_hook_source_branch=self)
1198
def is_locked(self):
1199
return self._lock_count >= 1
1201
def set_last_revision_info(self, revno, revision_id):
1203
self._clear_cached_state()
1204
return self._real_branch.set_last_revision_info(revno, revision_id)
1206
def generate_revision_history(self, revision_id, last_rev=None,
1209
return self._real_branch.generate_revision_history(
1210
revision_id, last_rev=last_rev, other_branch=other_branch)
1215
return self._real_branch.tags
1217
def set_push_location(self, location):
1219
return self._real_branch.set_push_location(location)
1221
def update_revisions(self, other, stop_revision=None):
1223
return self._real_branch.update_revisions(
1224
other, stop_revision=stop_revision)
1227
class RemoteBranchConfig(BranchConfig):
1230
self.branch._ensure_real()
1231
return self.branch._real_branch.get_config().username()
1233
def _get_branch_data_config(self):
1234
self.branch._ensure_real()
1235
if self._branch_data_config is None:
1236
self._branch_data_config = TreeConfig(self.branch._real_branch)
1237
return self._branch_data_config
1240
def _extract_tar(tar, to_dir):
1241
"""Extract all the contents of a tarfile object.
1243
A replacement for extractall, which is not present in python2.4
1246
tar.extract(tarinfo, to_dir)