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
22
from bzrlib import branch, errors, lockdir, repository
23
from bzrlib.branch import BranchReferenceFormat
24
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
25
from bzrlib.config import BranchConfig, TreeConfig
26
from bzrlib.decorators import needs_read_lock, needs_write_lock
27
from bzrlib.errors import NoSuchRevision
28
from bzrlib.lockable_files import LockableFiles
29
from bzrlib.revision import NULL_REVISION
30
from bzrlib.smart import client, vfs
32
# Note: RemoteBzrDirFormat is in bzrdir.py
34
class RemoteBzrDir(BzrDir):
35
"""Control directory on a remote server, accessed via bzr:// or similar."""
37
def __init__(self, transport, _client=None):
38
"""Construct a RemoteBzrDir.
40
:param _client: Private parameter for testing. Disables probing and the
43
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
44
# this object holds a delegated bzrdir that uses file-level operations
45
# to talk to the other side
46
self._real_bzrdir = None
49
self._medium = transport.get_smart_client()
50
self._client = client._SmartClient(self._medium)
52
self._client = _client
57
path = self._path_for_remote_call(self._client)
58
response = self._client.call('BzrDir.open', path)
59
if response not in [('yes',), ('no',)]:
60
raise errors.UnexpectedSmartServerResponse(response)
61
if response == ('no',):
62
raise errors.NotBranchError(path=transport.base)
64
def _ensure_real(self):
65
"""Ensure that there is a _real_bzrdir set.
67
Used before calls to self._real_bzrdir.
69
if not self._real_bzrdir:
70
self._real_bzrdir = BzrDir.open_from_transport(
71
self.root_transport, _server_formats=False)
73
def create_repository(self, shared=False):
75
self._real_bzrdir.create_repository(shared=shared)
76
return self.open_repository()
78
def create_branch(self):
80
real_branch = self._real_bzrdir.create_branch()
81
return RemoteBranch(self, self.find_repository(), real_branch)
83
def create_workingtree(self, revision_id=None):
84
raise errors.NotLocalUrl(self.transport.base)
86
def find_branch_format(self):
87
"""Find the branch 'format' for this bzrdir.
89
This might be a synthetic object for e.g. RemoteBranch and SVN.
91
b = self.open_branch()
94
def get_branch_reference(self):
95
"""See BzrDir.get_branch_reference()."""
96
path = self._path_for_remote_call(self._client)
97
response = self._client.call('BzrDir.open_branch', path)
98
if response[0] == 'ok':
100
# branch at this location.
103
# a branch reference, use the existing BranchReference logic.
105
elif response == ('nobranch',):
106
raise errors.NotBranchError(path=self.root_transport.base)
108
assert False, 'unexpected response code %r' % (response,)
110
def open_branch(self, _unsupported=False):
111
assert _unsupported == False, 'unsupported flag support not implemented yet.'
112
reference_url = self.get_branch_reference()
113
if reference_url is None:
114
# branch at this location.
115
return RemoteBranch(self, self.find_repository())
117
# a branch reference, use the existing BranchReference logic.
118
format = BranchReferenceFormat()
119
return format.open(self, _found=True, location=reference_url)
121
def open_repository(self):
122
path = self._path_for_remote_call(self._client)
123
response = self._client.call('BzrDir.find_repository', path)
124
assert response[0] in ('ok', 'norepository'), \
125
'unexpected response code %s' % (response,)
126
if response[0] == 'norepository':
127
raise errors.NoRepositoryPresent(self)
128
assert len(response) == 4, 'incorrect response length %s' % (response,)
129
if response[1] == '':
130
format = RemoteRepositoryFormat()
131
format.rich_root_data = (response[2] == 'yes')
132
format.supports_tree_reference = (response[3] == 'yes')
133
return RemoteRepository(self, format)
135
raise errors.NoRepositoryPresent(self)
137
def open_workingtree(self, recommend_upgrade=True):
138
raise errors.NotLocalUrl(self.root_transport)
140
def _path_for_remote_call(self, client):
141
"""Return the path to be used for this bzrdir in a remote call."""
142
return client.remote_path_from_transport(self.root_transport)
144
def get_branch_transport(self, branch_format):
146
return self._real_bzrdir.get_branch_transport(branch_format)
148
def get_repository_transport(self, repository_format):
150
return self._real_bzrdir.get_repository_transport(repository_format)
152
def get_workingtree_transport(self, workingtree_format):
154
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
156
def can_convert_format(self):
157
"""Upgrading of remote bzrdirs is not supported yet."""
160
def needs_format_conversion(self, format=None):
161
"""Upgrading of remote bzrdirs is not supported yet."""
164
def clone(self, url, revision_id=None, force_new_repo=False):
166
return self._real_bzrdir.clone(url, revision_id=revision_id,
167
force_new_repo=force_new_repo)
170
class RemoteRepositoryFormat(repository.RepositoryFormat):
171
"""Format for repositories accessed over a _SmartClient.
173
Instances of this repository are represented by RemoteRepository
176
The RemoteRepositoryFormat is parameterised during construction
177
to reflect the capabilities of the real, remote format. Specifically
178
the attributes rich_root_data and supports_tree_reference are set
179
on a per instance basis, and are not set (and should not be) at
183
_matchingbzrdir = RemoteBzrDirFormat
185
def initialize(self, a_bzrdir, shared=False):
186
assert isinstance(a_bzrdir, RemoteBzrDir), \
187
'%r is not a RemoteBzrDir' % (a_bzrdir,)
188
return a_bzrdir.create_repository(shared=shared)
190
def open(self, a_bzrdir):
191
assert isinstance(a_bzrdir, RemoteBzrDir)
192
return a_bzrdir.open_repository()
194
def get_format_description(self):
195
return 'bzr remote repository'
197
def __eq__(self, other):
198
return self.__class__ == other.__class__
200
def check_conversion_target(self, target_format):
201
if self.rich_root_data and not target_format.rich_root_data:
202
raise errors.BadConversionTarget(
203
'Does not support rich root data.', target_format)
204
if (self.supports_tree_reference and
205
not getattr(target_format, 'supports_tree_reference', False)):
206
raise errors.BadConversionTarget(
207
'Does not support nested trees', target_format)
210
class RemoteRepository(object):
211
"""Repository accessed over rpc.
213
For the moment most operations are performed using local transport-backed
217
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
218
"""Create a RemoteRepository instance.
220
:param remote_bzrdir: The bzrdir hosting this repository.
221
:param format: The RemoteFormat object to use.
222
:param real_repository: If not None, a local implementation of the
223
repository logic for the repository, usually accessing the data
225
:param _client: Private testing parameter - override the smart client
226
to be used by the repository.
229
self._real_repository = real_repository
231
self._real_repository = None
232
self.bzrdir = remote_bzrdir
234
self._client = client._SmartClient(self.bzrdir._medium)
236
self._client = _client
237
self._format = format
238
self._lock_mode = None
239
self._lock_token = None
241
self._leave_lock = False
243
def _ensure_real(self):
244
"""Ensure that there is a _real_repository set.
246
Used before calls to self._real_repository.
248
if not self._real_repository:
249
self.bzrdir._ensure_real()
250
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
251
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
253
def get_revision_graph(self, revision_id=None):
254
"""See Repository.get_revision_graph()."""
255
if revision_id is None:
257
elif revision_id == NULL_REVISION:
260
path = self.bzrdir._path_for_remote_call(self._client)
261
assert type(revision_id) is str
262
response = self._client.call_expecting_body(
263
'Repository.get_revision_graph', path, revision_id)
264
if response[0][0] not in ['ok', 'nosuchrevision']:
265
raise errors.UnexpectedSmartServerResponse(response[0])
266
if response[0][0] == 'ok':
267
coded = response[1].read_body_bytes()
269
# no revisions in this repository!
271
lines = coded.split('\n')
274
d = list(line.split())
275
revision_graph[d[0]] = d[1:]
277
return revision_graph
279
response_body = response[1].read_body_bytes()
280
assert response_body == ''
281
raise NoSuchRevision(self, revision_id)
283
def has_revision(self, revision_id):
284
"""See Repository.has_revision()."""
285
if revision_id is None:
286
# The null revision is always present.
288
path = self.bzrdir._path_for_remote_call(self._client)
289
response = self._client.call('Repository.has_revision', path, revision_id)
290
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
291
return response[0] == 'yes'
293
def gather_stats(self, revid=None, committers=None):
294
"""See Repository.gather_stats()."""
295
path = self.bzrdir._path_for_remote_call(self._client)
296
if revid in (None, NULL_REVISION):
300
if committers is None or not committers:
301
fmt_committers = 'no'
303
fmt_committers = 'yes'
304
response = self._client.call_expecting_body(
305
'Repository.gather_stats', path, fmt_revid, fmt_committers)
306
assert response[0][0] == 'ok', \
307
'unexpected response code %s' % (response[0],)
309
body = response[1].read_body_bytes()
311
for line in body.split('\n'):
314
key, val_text = line.split(':')
315
if key in ('revisions', 'size', 'committers'):
316
result[key] = int(val_text)
317
elif key in ('firstrev', 'latestrev'):
318
values = val_text.split(' ')[1:]
319
result[key] = (float(values[0]), long(values[1]))
323
def get_physical_lock_status(self):
324
"""See Repository.get_physical_lock_status()."""
328
"""See Repository.is_shared()."""
329
path = self.bzrdir._path_for_remote_call(self._client)
330
response = self._client.call('Repository.is_shared', path)
331
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
332
return response[0] == 'yes'
335
# wrong eventually - want a local lock cache context
336
if not self._lock_mode:
337
self._lock_mode = 'r'
339
if self._real_repository is not None:
340
self._real_repository.lock_read()
342
self._lock_count += 1
344
def _remote_lock_write(self, token):
345
path = self.bzrdir._path_for_remote_call(self._client)
348
response = self._client.call('Repository.lock_write', path, token)
349
if response[0] == 'ok':
352
elif response[0] == 'LockContention':
353
raise errors.LockContention('(remote lock)')
354
elif response[0] == 'UnlockableTransport':
355
raise errors.UnlockableTransport(self.bzrdir.root_transport)
357
assert False, 'unexpected response code %s' % (response,)
359
def lock_write(self, token=None):
360
if not self._lock_mode:
361
self._lock_token = self._remote_lock_write(token)
362
assert self._lock_token, 'Remote server did not return a token!'
363
if self._real_repository is not None:
364
self._real_repository.lock_write(token=self._lock_token)
365
if token is not None:
366
self._leave_lock = True
368
self._leave_lock = False
369
self._lock_mode = 'w'
371
elif self._lock_mode == 'r':
372
raise errors.ReadOnlyError(self)
374
self._lock_count += 1
375
return self._lock_token
377
def leave_lock_in_place(self):
378
self._leave_lock = True
380
def dont_leave_lock_in_place(self):
381
self._leave_lock = False
383
def _set_real_repository(self, repository):
384
"""Set the _real_repository for this repository.
386
:param repository: The repository to fallback to for non-hpss
387
implemented operations.
389
assert not isinstance(repository, RemoteRepository)
390
self._real_repository = repository
391
if self._lock_mode == 'w':
392
# if we are already locked, the real repository must be able to
393
# acquire the lock with our token.
394
self._real_repository.lock_write(self._lock_token)
395
elif self._lock_mode == 'r':
396
self._real_repository.lock_read()
398
def _unlock(self, token):
399
path = self.bzrdir._path_for_remote_call(self._client)
400
response = self._client.call('Repository.unlock', path, token)
401
if response == ('ok',):
403
elif response[0] == 'TokenMismatch':
404
raise errors.TokenMismatch(token, '(remote token)')
406
assert False, 'unexpected response code %s' % (response,)
409
self._lock_count -= 1
410
if not self._lock_count:
411
mode = self._lock_mode
412
self._lock_mode = None
413
if self._real_repository is not None:
414
self._real_repository.unlock()
416
# Only write-locked repositories need to make a remote method
417
# call to perfom the unlock.
419
assert self._lock_token, 'Locked, but no token!'
420
token = self._lock_token
421
self._lock_token = None
422
if not self._leave_lock:
425
def break_lock(self):
426
# should hand off to the network
428
return self._real_repository.break_lock()
430
### These methods are just thin shims to the VFS object for now.
432
def revision_tree(self, revision_id):
434
return self._real_repository.revision_tree(revision_id)
436
def get_commit_builder(self, branch, parents, config, timestamp=None,
437
timezone=None, committer=None, revprops=None,
439
# FIXME: It ought to be possible to call this without immediately
440
# triggering _ensure_real. For now it's the easiest thing to do.
442
builder = self._real_repository.get_commit_builder(branch, parents,
443
config, timestamp=timestamp, timezone=timezone,
444
committer=committer, revprops=revprops, revision_id=revision_id)
445
# Make the builder use this RemoteRepository rather than the real one.
446
builder.repository = self
450
def add_inventory(self, revid, inv, parents):
452
return self._real_repository.add_inventory(revid, inv, parents)
455
def add_revision(self, rev_id, rev, inv=None, config=None):
457
return self._real_repository.add_revision(
458
rev_id, rev, inv=inv, config=config)
461
def get_inventory(self, revision_id):
463
return self._real_repository.get_inventory(revision_id)
466
def get_revision(self, revision_id):
468
return self._real_repository.get_revision(revision_id)
471
def weave_store(self):
473
return self._real_repository.weave_store
475
def get_transaction(self):
477
return self._real_repository.get_transaction()
480
def clone(self, a_bzrdir, revision_id=None):
482
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
484
def make_working_trees(self):
485
"""RemoteRepositories never create working trees by default."""
488
def fetch(self, source, revision_id=None, pb=None):
490
return self._real_repository.fetch(
491
source, revision_id=revision_id, pb=pb)
494
def control_weaves(self):
496
return self._real_repository.control_weaves
499
def get_ancestry(self, revision_id):
501
return self._real_repository.get_ancestry(revision_id)
504
def get_inventory_weave(self):
506
return self._real_repository.get_inventory_weave()
508
def fileids_altered_by_revision_ids(self, revision_ids):
510
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
513
def get_signature_text(self, revision_id):
515
return self._real_repository.get_signature_text(revision_id)
518
def get_revision_graph_with_ghosts(self, revision_ids=None):
520
return self._real_repository.get_revision_graph_with_ghosts(
521
revision_ids=revision_ids)
524
def get_inventory_xml(self, revision_id):
526
return self._real_repository.get_inventory_xml(revision_id)
528
def deserialise_inventory(self, revision_id, xml):
530
return self._real_repository.deserialise_inventory(revision_id, xml)
532
def reconcile(self, other=None, thorough=False):
534
return self._real_repository.reconcile(other=other, thorough=thorough)
536
def all_revision_ids(self):
538
return self._real_repository.all_revision_ids()
541
def get_deltas_for_revisions(self, revisions):
543
return self._real_repository.get_deltas_for_revisions(revisions)
546
def get_revision_delta(self, revision_id):
548
return self._real_repository.get_revision_delta(revision_id)
551
def revision_trees(self, revision_ids):
553
return self._real_repository.revision_trees(revision_ids)
556
def get_revision_reconcile(self, revision_id):
558
return self._real_repository.get_revision_reconcile(revision_id)
561
def check(self, revision_ids):
563
return self._real_repository.check(revision_ids)
565
def copy_content_into(self, destination, revision_id=None):
567
return self._real_repository.copy_content_into(
568
destination, revision_id=revision_id)
570
def set_make_working_trees(self, new_value):
571
raise NotImplementedError(self.set_make_working_trees)
574
def sign_revision(self, revision_id, gpg_strategy):
576
return self._real_repository.sign_revision(revision_id, gpg_strategy)
579
def get_revisions(self, revision_ids):
581
return self._real_repository.get_revisions(revision_ids)
583
def supports_rich_root(self):
585
return self._real_repository.supports_rich_root()
587
def iter_reverse_revision_history(self, revision_id):
589
return self._real_repository.iter_reverse_revision_history(revision_id)
592
def _serializer(self):
594
return self._real_repository._serializer
596
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
598
return self._real_repository.store_revision_signature(
599
gpg_strategy, plaintext, revision_id)
601
def has_signature_for_revision_id(self, revision_id):
603
return self._real_repository.has_signature_for_revision_id(revision_id)
606
class RemoteBranchLockableFiles(LockableFiles):
607
"""A 'LockableFiles' implementation that talks to a smart server.
609
This is not a public interface class.
612
def __init__(self, bzrdir, _client):
614
self._client = _client
615
self._need_find_modes = True
616
LockableFiles.__init__(
617
self, bzrdir.get_branch_transport(None),
618
'lock', lockdir.LockDir)
620
def _find_modes(self):
621
# RemoteBranches don't let the client set the mode of control files.
622
self._dir_mode = None
623
self._file_mode = None
626
"""'get' a remote path as per the LockableFiles interface.
628
:param path: the file to 'get'. If this is 'branch.conf', we do not
629
just retrieve a file, instead we ask the smart server to generate
630
a configuration for us - which is retrieved as an INI file.
632
if path == 'branch.conf':
633
path = self.bzrdir._path_for_remote_call(self._client)
634
response = self._client.call_expecting_body(
635
'Branch.get_config_file', path)
636
assert response[0][0] == 'ok', \
637
'unexpected response code %s' % (response[0],)
638
return StringIO(response[1].read_body_bytes())
641
return LockableFiles.get(self, path)
644
class RemoteBranchFormat(branch.BranchFormat):
646
def __eq__(self, other):
647
return (isinstance(other, RemoteBranchFormat) and
648
self.__dict__ == other.__dict__)
650
def get_format_description(self):
651
return 'Remote BZR Branch'
653
def get_format_string(self):
654
return 'Remote BZR Branch'
656
def open(self, a_bzrdir):
657
assert isinstance(a_bzrdir, RemoteBzrDir)
658
return a_bzrdir.open_branch()
660
def initialize(self, a_bzrdir):
661
assert isinstance(a_bzrdir, RemoteBzrDir)
662
return a_bzrdir.create_branch()
665
class RemoteBranch(branch.Branch):
666
"""Branch stored on a server accessed by HPSS RPC.
668
At the moment most operations are mapped down to simple file operations.
671
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
673
"""Create a RemoteBranch instance.
675
:param real_branch: An optional local implementation of the branch
676
format, usually accessing the data via the VFS.
677
:param _client: Private parameter for testing.
679
# We intentionally don't call the parent class's __init__, because it
680
# will try to assign to self.tags, which is a property in this subclass.
681
# And the parent's __init__ doesn't do much anyway.
682
self._revision_history_cache = None
683
self.bzrdir = remote_bzrdir
684
if _client is not None:
685
self._client = _client
687
self._client = client._SmartClient(self.bzrdir._medium)
688
self.repository = remote_repository
689
if real_branch is not None:
690
self._real_branch = real_branch
691
# Give the remote repository the matching real repo.
692
real_repo = self._real_branch.repository
693
if isinstance(real_repo, RemoteRepository):
694
real_repo._ensure_real()
695
real_repo = real_repo._real_repository
696
self.repository._set_real_repository(real_repo)
697
# Give the branch the remote repository to let fast-pathing happen.
698
self._real_branch.repository = self.repository
700
self._real_branch = None
701
# Fill out expected attributes of branch for bzrlib api users.
702
self._format = RemoteBranchFormat()
703
self.base = self.bzrdir.root_transport.base
704
self._control_files = None
705
self._lock_mode = None
706
self._lock_token = None
708
self._leave_lock = False
710
def _ensure_real(self):
711
"""Ensure that there is a _real_branch set.
713
Used before calls to self._real_branch.
715
if not self._real_branch:
716
assert vfs.vfs_enabled()
717
self.bzrdir._ensure_real()
718
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
719
# Give the remote repository the matching real repo.
720
real_repo = self._real_branch.repository
721
if isinstance(real_repo, RemoteRepository):
722
real_repo._ensure_real()
723
real_repo = real_repo._real_repository
724
self.repository._set_real_repository(real_repo)
725
# Give the branch the remote repository to let fast-pathing happen.
726
self._real_branch.repository = self.repository
727
# XXX: deal with _lock_mode == 'w'
728
if self._lock_mode == 'r':
729
self._real_branch.lock_read()
732
def control_files(self):
733
# Defer actually creating RemoteBranchLockableFiles until its needed,
734
# because it triggers an _ensure_real that we otherwise might not need.
735
if self._control_files is None:
736
self._control_files = RemoteBranchLockableFiles(
737
self.bzrdir, self._client)
738
return self._control_files
740
def _get_checkout_format(self):
742
return self._real_branch._get_checkout_format()
744
def get_physical_lock_status(self):
745
"""See Branch.get_physical_lock_status()."""
746
# should be an API call to the server, as branches must be lockable.
748
return self._real_branch.get_physical_lock_status()
751
if not self._lock_mode:
752
self._lock_mode = 'r'
754
if self._real_branch is not None:
755
self._real_branch.lock_read()
757
self._lock_count += 1
759
def _remote_lock_write(self, token):
761
branch_token = repo_token = ''
764
repo_token = self.repository.lock_write()
765
self.repository.unlock()
766
path = self.bzrdir._path_for_remote_call(self._client)
767
response = self._client.call('Branch.lock_write', path, branch_token,
769
if response[0] == 'ok':
770
ok, branch_token, repo_token = response
771
return branch_token, repo_token
772
elif response[0] == 'LockContention':
773
raise errors.LockContention('(remote lock)')
774
elif response[0] == 'TokenMismatch':
775
raise errors.TokenMismatch(token, '(remote token)')
776
elif response[0] == 'UnlockableTransport':
777
raise errors.UnlockableTransport(self.bzrdir.root_transport)
778
elif response[0] == 'ReadOnlyError':
779
raise errors.ReadOnlyError(self)
781
assert False, 'unexpected response code %r' % (response,)
783
def lock_write(self, token=None):
784
if not self._lock_mode:
785
remote_tokens = self._remote_lock_write(token)
786
self._lock_token, self._repo_lock_token = remote_tokens
787
assert self._lock_token, 'Remote server did not return a token!'
788
# TODO: We really, really, really don't want to call _ensure_real
789
# here, but it's the easiest way to ensure coherency between the
790
# state of the RemoteBranch and RemoteRepository objects and the
791
# physical locks. If we don't materialise the real objects here,
792
# then getting everything in the right state later is complex, so
793
# for now we just do it the lazy way.
794
# -- Andrew Bennetts, 2007-02-22.
796
if self._real_branch is not None:
797
self._real_branch.repository.lock_write(
798
token=self._repo_lock_token)
800
self._real_branch.lock_write(token=self._lock_token)
802
self._real_branch.repository.unlock()
803
if token is not None:
804
self._leave_lock = True
806
# XXX: this case seems to be unreachable; token cannot be None.
807
self._leave_lock = False
808
self._lock_mode = 'w'
810
elif self._lock_mode == 'r':
811
raise errors.ReadOnlyTransaction
813
if token is not None:
814
# A token was given to lock_write, and we're relocking, so check
815
# that the given token actually matches the one we already have.
816
if token != self._lock_token:
817
raise errors.TokenMismatch(token, self._lock_token)
818
self._lock_count += 1
819
return self._lock_token
821
def _unlock(self, branch_token, repo_token):
822
path = self.bzrdir._path_for_remote_call(self._client)
823
response = self._client.call('Branch.unlock', path, branch_token,
825
if response == ('ok',):
827
elif response[0] == 'TokenMismatch':
828
raise errors.TokenMismatch(
829
str((branch_token, repo_token)), '(remote tokens)')
831
assert False, 'unexpected response code %s' % (response,)
834
self._lock_count -= 1
835
if not self._lock_count:
836
self._clear_cached_state()
837
mode = self._lock_mode
838
self._lock_mode = None
839
if self._real_branch is not None:
840
if not self._leave_lock:
841
# If this RemoteBranch will remove the physical lock for the
842
# repository, make sure the _real_branch doesn't do it
843
# first. (Because the _real_branch's repository is set to
844
# be the RemoteRepository.)
845
self._real_branch.repository.leave_lock_in_place()
846
self._real_branch.unlock()
848
# Only write-locked branched need to make a remote method call
849
# to perfom the unlock.
851
assert self._lock_token, 'Locked, but no token!'
852
branch_token = self._lock_token
853
repo_token = self._repo_lock_token
854
self._lock_token = None
855
self._repo_lock_token = None
856
if not self._leave_lock:
857
self._unlock(branch_token, repo_token)
859
def break_lock(self):
861
return self._real_branch.break_lock()
863
def leave_lock_in_place(self):
864
self._leave_lock = True
866
def dont_leave_lock_in_place(self):
867
self._leave_lock = False
869
def last_revision_info(self):
870
"""See Branch.last_revision_info()."""
871
path = self.bzrdir._path_for_remote_call(self._client)
872
response = self._client.call('Branch.last_revision_info', path)
873
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
874
revno = int(response[1])
875
last_revision = response[2]
876
return (revno, last_revision)
878
def _gen_revision_history(self):
879
"""See Branch._gen_revision_history()."""
880
path = self.bzrdir._path_for_remote_call(self._client)
881
response = self._client.call_expecting_body(
882
'Branch.revision_history', path)
883
assert response[0][0] == 'ok', ('unexpected response code %s'
885
result = response[1].read_body_bytes().split('\x00')
891
def set_revision_history(self, rev_history):
892
# Send just the tip revision of the history; the server will generate
893
# the full history from that. If the revision doesn't exist in this
894
# branch, NoSuchRevision will be raised.
895
path = self.bzrdir._path_for_remote_call(self._client)
896
if rev_history == []:
899
rev_id = rev_history[-1]
900
response = self._client.call('Branch.set_last_revision',
901
path, self._lock_token, self._repo_lock_token, rev_id)
902
if response[0] == 'NoSuchRevision':
903
raise NoSuchRevision(self, rev_id)
905
assert response == ('ok',), (
906
'unexpected response code %r' % (response,))
907
self._cache_revision_history(rev_history)
909
def get_parent(self):
911
return self._real_branch.get_parent()
913
def set_parent(self, url):
915
return self._real_branch.set_parent(url)
917
def get_config(self):
918
return RemoteBranchConfig(self)
920
def sprout(self, to_bzrdir, revision_id=None):
921
# Like Branch.sprout, except that it sprouts a branch in the default
922
# format, because RemoteBranches can't be created at arbitrary URLs.
923
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
924
# to_bzrdir.create_branch...
926
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
927
self._real_branch.copy_content_into(result, revision_id=revision_id)
928
result.set_parent(self.bzrdir.root_transport.base)
932
def append_revision(self, *revision_ids):
934
return self._real_branch.append_revision(*revision_ids)
937
def pull(self, source, overwrite=False, stop_revision=None):
939
self._real_branch.pull(
940
source, overwrite=overwrite, stop_revision=stop_revision)
943
def push(self, target, overwrite=False, stop_revision=None):
945
return self._real_branch.push(
946
target, overwrite=overwrite, stop_revision=stop_revision)
949
return self._lock_count >= 1
951
def set_last_revision_info(self, revno, revision_id):
953
self._clear_cached_state()
954
return self._real_branch.set_last_revision_info(revno, revision_id)
956
def generate_revision_history(self, revision_id, last_rev=None,
959
return self._real_branch.generate_revision_history(
960
revision_id, last_rev=last_rev, other_branch=other_branch)
965
return self._real_branch.tags
967
def set_push_location(self, location):
969
return self._real_branch.set_push_location(location)
971
def update_revisions(self, other, stop_revision=None):
973
return self._real_branch.update_revisions(
974
other, stop_revision=stop_revision)
977
class RemoteBranchConfig(BranchConfig):
980
self.branch._ensure_real()
981
return self.branch._real_branch.get_config().username()
983
def _get_branch_data_config(self):
984
self.branch._ensure_real()
985
if self._branch_data_config is None:
986
self._branch_data_config = TreeConfig(self.branch._real_branch)
987
return self._branch_data_config