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
56
path = self._path_for_remote_call(self._client)
57
response = self._client.call('BzrDir.open', path)
58
if response not in [('yes',), ('no',)]:
59
raise errors.UnexpectedSmartServerResponse(response)
60
if response == ('no',):
61
raise errors.NotBranchError(path=transport.base)
63
def _ensure_real(self):
64
"""Ensure that there is a _real_bzrdir set.
66
Used before calls to self._real_bzrdir.
68
if not self._real_bzrdir:
69
self._real_bzrdir = BzrDir.open_from_transport(
70
self.root_transport, _server_formats=False)
72
def create_repository(self, shared=False):
74
self._real_bzrdir.create_repository(shared=shared)
75
return self.open_repository()
77
def create_branch(self):
79
real_branch = self._real_bzrdir.create_branch()
80
return RemoteBranch(self, self.find_repository(), real_branch)
82
def create_workingtree(self, revision_id=None):
83
raise errors.NotLocalUrl(self.transport.base)
85
def find_branch_format(self):
86
"""Find the branch 'format' for this bzrdir.
88
This might be a synthetic object for e.g. RemoteBranch and SVN.
90
b = self.open_branch()
93
def get_branch_reference(self):
94
"""See BzrDir.get_branch_reference()."""
95
path = self._path_for_remote_call(self._client)
96
response = self._client.call('BzrDir.open_branch', path)
97
if response[0] == 'ok':
99
# branch at this location.
102
# a branch reference, use the existing BranchReference logic.
104
elif response == ('nobranch',):
105
raise errors.NotBranchError(path=self.root_transport.base)
107
assert False, 'unexpected response code %r' % (response,)
109
def open_branch(self, _unsupported=False):
110
assert _unsupported == False, 'unsupported flag support not implemented yet.'
111
reference_url = self.get_branch_reference()
112
if reference_url is None:
113
# branch at this location.
114
return RemoteBranch(self, self.find_repository())
116
# a branch reference, use the existing BranchReference logic.
117
format = BranchReferenceFormat()
118
return format.open(self, _found=True, location=reference_url)
120
def open_repository(self):
121
path = self._path_for_remote_call(self._client)
122
response = self._client.call('BzrDir.find_repository', path)
123
assert response[0] in ('ok', 'norepository'), \
124
'unexpected response code %s' % (response,)
125
if response[0] == 'norepository':
126
raise errors.NoRepositoryPresent(self)
127
assert len(response) == 4, 'incorrect response length %s' % (response,)
128
if response[1] == '':
129
format = RemoteRepositoryFormat()
130
format.rich_root_data = (response[2] == 'yes')
131
format.supports_tree_reference = (response[3] == 'yes')
132
return RemoteRepository(self, format)
134
raise errors.NoRepositoryPresent(self)
136
def open_workingtree(self, recommend_upgrade=True):
138
if self._real_bzrdir.has_workingtree():
139
raise errors.NotLocalUrl(self.root_transport)
141
raise errors.NoWorkingTree(self.root_transport.base)
143
def _path_for_remote_call(self, client):
144
"""Return the path to be used for this bzrdir in a remote call."""
145
return client.remote_path_from_transport(self.root_transport)
147
def get_branch_transport(self, branch_format):
149
return self._real_bzrdir.get_branch_transport(branch_format)
151
def get_repository_transport(self, repository_format):
153
return self._real_bzrdir.get_repository_transport(repository_format)
155
def get_workingtree_transport(self, workingtree_format):
157
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
159
def can_convert_format(self):
160
"""Upgrading of remote bzrdirs is not supported yet."""
163
def needs_format_conversion(self, format=None):
164
"""Upgrading of remote bzrdirs is not supported yet."""
167
def clone(self, url, revision_id=None, force_new_repo=False):
169
return self._real_bzrdir.clone(url, revision_id=revision_id,
170
force_new_repo=force_new_repo)
173
class RemoteRepositoryFormat(repository.RepositoryFormat):
174
"""Format for repositories accessed over a _SmartClient.
176
Instances of this repository are represented by RemoteRepository
179
The RemoteRepositoryFormat is parameterised during construction
180
to reflect the capabilities of the real, remote format. Specifically
181
the attributes rich_root_data and supports_tree_reference are set
182
on a per instance basis, and are not set (and should not be) at
186
_matchingbzrdir = RemoteBzrDirFormat
188
def initialize(self, a_bzrdir, shared=False):
189
assert isinstance(a_bzrdir, RemoteBzrDir), \
190
'%r is not a RemoteBzrDir' % (a_bzrdir,)
191
return a_bzrdir.create_repository(shared=shared)
193
def open(self, a_bzrdir):
194
assert isinstance(a_bzrdir, RemoteBzrDir)
195
return a_bzrdir.open_repository()
197
def get_format_description(self):
198
return 'bzr remote repository'
200
def __eq__(self, other):
201
return self.__class__ == other.__class__
203
def check_conversion_target(self, target_format):
204
if self.rich_root_data and not target_format.rich_root_data:
205
raise errors.BadConversionTarget(
206
'Does not support rich root data.', target_format)
207
if (self.supports_tree_reference and
208
not getattr(target_format, 'supports_tree_reference', False)):
209
raise errors.BadConversionTarget(
210
'Does not support nested trees', target_format)
213
class RemoteRepository(object):
214
"""Repository accessed over rpc.
216
For the moment most operations are performed using local transport-backed
220
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
221
"""Create a RemoteRepository instance.
223
:param remote_bzrdir: The bzrdir hosting this repository.
224
:param format: The RemoteFormat object to use.
225
:param real_repository: If not None, a local implementation of the
226
repository logic for the repository, usually accessing the data
228
:param _client: Private testing parameter - override the smart client
229
to be used by the repository.
232
self._real_repository = real_repository
234
self._real_repository = None
235
self.bzrdir = remote_bzrdir
237
self._client = client._SmartClient(self.bzrdir._medium)
239
self._client = _client
240
self._format = format
241
self._lock_mode = None
242
self._lock_token = None
244
self._leave_lock = False
246
def _ensure_real(self):
247
"""Ensure that there is a _real_repository set.
249
Used before calls to self._real_repository.
251
if not self._real_repository:
252
self.bzrdir._ensure_real()
253
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
254
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
256
def get_revision_graph(self, revision_id=None):
257
"""See Repository.get_revision_graph()."""
258
if revision_id is None:
260
elif revision_id == NULL_REVISION:
263
path = self.bzrdir._path_for_remote_call(self._client)
264
assert type(revision_id) is str
265
response = self._client.call_expecting_body(
266
'Repository.get_revision_graph', path, revision_id)
267
if response[0][0] not in ['ok', 'nosuchrevision']:
268
raise errors.UnexpectedSmartServerResponse(response[0])
269
if response[0][0] == 'ok':
270
coded = response[1].read_body_bytes()
272
# no revisions in this repository!
274
lines = coded.split('\n')
277
d = list(line.split())
278
revision_graph[d[0]] = d[1:]
280
return revision_graph
282
response_body = response[1].read_body_bytes()
283
assert response_body == ''
284
raise NoSuchRevision(self, revision_id)
286
def has_revision(self, revision_id):
287
"""See Repository.has_revision()."""
288
if revision_id is None:
289
# The null revision is always present.
291
path = self.bzrdir._path_for_remote_call(self._client)
292
response = self._client.call('Repository.has_revision', path, revision_id)
293
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
294
return response[0] == 'yes'
296
def gather_stats(self, revid=None, committers=None):
297
"""See Repository.gather_stats()."""
298
path = self.bzrdir._path_for_remote_call(self._client)
299
if revid in (None, NULL_REVISION):
303
if committers is None or not committers:
304
fmt_committers = 'no'
306
fmt_committers = 'yes'
307
response = self._client.call_expecting_body(
308
'Repository.gather_stats', path, fmt_revid, fmt_committers)
309
assert response[0][0] == 'ok', \
310
'unexpected response code %s' % (response[0],)
312
body = response[1].read_body_bytes()
314
for line in body.split('\n'):
317
key, val_text = line.split(':')
318
if key in ('revisions', 'size', 'committers'):
319
result[key] = int(val_text)
320
elif key in ('firstrev', 'latestrev'):
321
values = val_text.split(' ')[1:]
322
result[key] = (float(values[0]), long(values[1]))
326
def get_physical_lock_status(self):
327
"""See Repository.get_physical_lock_status()."""
331
"""See Repository.is_shared()."""
332
path = self.bzrdir._path_for_remote_call(self._client)
333
response = self._client.call('Repository.is_shared', path)
334
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
335
return response[0] == 'yes'
338
# wrong eventually - want a local lock cache context
339
if not self._lock_mode:
340
self._lock_mode = 'r'
342
if self._real_repository is not None:
343
self._real_repository.lock_read()
345
self._lock_count += 1
347
def _remote_lock_write(self, token):
348
path = self.bzrdir._path_for_remote_call(self._client)
351
response = self._client.call('Repository.lock_write', path, token)
352
if response[0] == 'ok':
355
elif response[0] == 'LockContention':
356
raise errors.LockContention('(remote lock)')
357
elif response[0] == 'UnlockableTransport':
358
raise errors.UnlockableTransport(self.bzrdir.root_transport)
360
assert False, 'unexpected response code %s' % (response,)
362
def lock_write(self, token=None):
363
if not self._lock_mode:
364
self._lock_token = self._remote_lock_write(token)
365
assert self._lock_token, 'Remote server did not return a token!'
366
if self._real_repository is not None:
367
self._real_repository.lock_write(token=self._lock_token)
368
if token is not None:
369
self._leave_lock = True
371
self._leave_lock = False
372
self._lock_mode = 'w'
374
elif self._lock_mode == 'r':
375
raise errors.ReadOnlyError(self)
377
self._lock_count += 1
378
return self._lock_token
380
def leave_lock_in_place(self):
381
self._leave_lock = True
383
def dont_leave_lock_in_place(self):
384
self._leave_lock = False
386
def _set_real_repository(self, repository):
387
"""Set the _real_repository for this repository.
389
:param repository: The repository to fallback to for non-hpss
390
implemented operations.
392
assert not isinstance(repository, RemoteRepository)
393
self._real_repository = repository
394
if self._lock_mode == 'w':
395
# if we are already locked, the real repository must be able to
396
# acquire the lock with our token.
397
self._real_repository.lock_write(self._lock_token)
398
elif self._lock_mode == 'r':
399
self._real_repository.lock_read()
401
def _unlock(self, token):
402
path = self.bzrdir._path_for_remote_call(self._client)
403
response = self._client.call('Repository.unlock', path, token)
404
if response == ('ok',):
406
elif response[0] == 'TokenMismatch':
407
raise errors.TokenMismatch(token, '(remote token)')
409
assert False, 'unexpected response code %s' % (response,)
412
self._lock_count -= 1
413
if not self._lock_count:
414
mode = self._lock_mode
415
self._lock_mode = None
416
if self._real_repository is not None:
417
self._real_repository.unlock()
419
# Only write-locked repositories need to make a remote method
420
# call to perfom the unlock.
422
assert self._lock_token, 'Locked, but no token!'
423
token = self._lock_token
424
self._lock_token = None
425
if not self._leave_lock:
428
def break_lock(self):
429
# should hand off to the network
431
return self._real_repository.break_lock()
433
### These methods are just thin shims to the VFS object for now.
435
def revision_tree(self, revision_id):
437
return self._real_repository.revision_tree(revision_id)
439
def get_commit_builder(self, branch, parents, config, timestamp=None,
440
timezone=None, committer=None, revprops=None,
442
# FIXME: It ought to be possible to call this without immediately
443
# triggering _ensure_real. For now it's the easiest thing to do.
445
builder = self._real_repository.get_commit_builder(branch, parents,
446
config, timestamp=timestamp, timezone=timezone,
447
committer=committer, revprops=revprops, revision_id=revision_id)
448
# Make the builder use this RemoteRepository rather than the real one.
449
builder.repository = self
453
def add_inventory(self, revid, inv, parents):
455
return self._real_repository.add_inventory(revid, inv, parents)
458
def add_revision(self, rev_id, rev, inv=None, config=None):
460
return self._real_repository.add_revision(
461
rev_id, rev, inv=inv, config=config)
464
def get_inventory(self, revision_id):
466
return self._real_repository.get_inventory(revision_id)
469
def get_revision(self, revision_id):
471
return self._real_repository.get_revision(revision_id)
474
def weave_store(self):
476
return self._real_repository.weave_store
478
def get_transaction(self):
480
return self._real_repository.get_transaction()
483
def clone(self, a_bzrdir, revision_id=None):
485
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
487
def make_working_trees(self):
488
"""RemoteRepositories never create working trees by default."""
491
def fetch(self, source, revision_id=None, pb=None):
493
return self._real_repository.fetch(
494
source, revision_id=revision_id, pb=pb)
497
def control_weaves(self):
499
return self._real_repository.control_weaves
502
def get_ancestry(self, revision_id):
504
return self._real_repository.get_ancestry(revision_id)
507
def get_inventory_weave(self):
509
return self._real_repository.get_inventory_weave()
511
def fileids_altered_by_revision_ids(self, revision_ids):
513
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
516
def get_signature_text(self, revision_id):
518
return self._real_repository.get_signature_text(revision_id)
521
def get_revision_graph_with_ghosts(self, revision_ids=None):
523
return self._real_repository.get_revision_graph_with_ghosts(
524
revision_ids=revision_ids)
527
def get_inventory_xml(self, revision_id):
529
return self._real_repository.get_inventory_xml(revision_id)
531
def deserialise_inventory(self, revision_id, xml):
533
return self._real_repository.deserialise_inventory(revision_id, xml)
535
def reconcile(self, other=None, thorough=False):
537
return self._real_repository.reconcile(other=other, thorough=thorough)
539
def all_revision_ids(self):
541
return self._real_repository.all_revision_ids()
544
def get_deltas_for_revisions(self, revisions):
546
return self._real_repository.get_deltas_for_revisions(revisions)
549
def get_revision_delta(self, revision_id):
551
return self._real_repository.get_revision_delta(revision_id)
554
def revision_trees(self, revision_ids):
556
return self._real_repository.revision_trees(revision_ids)
559
def get_revision_reconcile(self, revision_id):
561
return self._real_repository.get_revision_reconcile(revision_id)
564
def check(self, revision_ids):
566
return self._real_repository.check(revision_ids)
568
def copy_content_into(self, destination, revision_id=None):
570
return self._real_repository.copy_content_into(
571
destination, revision_id=revision_id)
573
def set_make_working_trees(self, new_value):
574
raise NotImplementedError(self.set_make_working_trees)
577
def sign_revision(self, revision_id, gpg_strategy):
579
return self._real_repository.sign_revision(revision_id, gpg_strategy)
582
def get_revisions(self, revision_ids):
584
return self._real_repository.get_revisions(revision_ids)
586
def supports_rich_root(self):
588
return self._real_repository.supports_rich_root()
590
def iter_reverse_revision_history(self, revision_id):
592
return self._real_repository.iter_reverse_revision_history(revision_id)
595
def _serializer(self):
597
return self._real_repository._serializer
599
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
601
return self._real_repository.store_revision_signature(
602
gpg_strategy, plaintext, revision_id)
604
def has_signature_for_revision_id(self, revision_id):
606
return self._real_repository.has_signature_for_revision_id(revision_id)
609
class RemoteBranchLockableFiles(LockableFiles):
610
"""A 'LockableFiles' implementation that talks to a smart server.
612
This is not a public interface class.
615
def __init__(self, bzrdir, _client):
617
self._client = _client
618
self._need_find_modes = True
619
LockableFiles.__init__(
620
self, bzrdir.get_branch_transport(None),
621
'lock', lockdir.LockDir)
623
def _find_modes(self):
624
# RemoteBranches don't let the client set the mode of control files.
625
self._dir_mode = None
626
self._file_mode = None
629
"""'get' a remote path as per the LockableFiles interface.
631
:param path: the file to 'get'. If this is 'branch.conf', we do not
632
just retrieve a file, instead we ask the smart server to generate
633
a configuration for us - which is retrieved as an INI file.
635
if path == 'branch.conf':
636
path = self.bzrdir._path_for_remote_call(self._client)
637
response = self._client.call_expecting_body(
638
'Branch.get_config_file', path)
639
assert response[0][0] == 'ok', \
640
'unexpected response code %s' % (response[0],)
641
return StringIO(response[1].read_body_bytes())
644
return LockableFiles.get(self, path)
647
class RemoteBranchFormat(branch.BranchFormat):
649
def __eq__(self, other):
650
return (isinstance(other, RemoteBranchFormat) and
651
self.__dict__ == other.__dict__)
653
def get_format_description(self):
654
return 'Remote BZR Branch'
656
def get_format_string(self):
657
return 'Remote BZR Branch'
659
def open(self, a_bzrdir):
660
assert isinstance(a_bzrdir, RemoteBzrDir)
661
return a_bzrdir.open_branch()
663
def initialize(self, a_bzrdir):
664
assert isinstance(a_bzrdir, RemoteBzrDir)
665
return a_bzrdir.create_branch()
668
class RemoteBranch(branch.Branch):
669
"""Branch stored on a server accessed by HPSS RPC.
671
At the moment most operations are mapped down to simple file operations.
674
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
676
"""Create a RemoteBranch instance.
678
:param real_branch: An optional local implementation of the branch
679
format, usually accessing the data via the VFS.
680
:param _client: Private parameter for testing.
682
# We intentionally don't call the parent class's __init__, because it
683
# will try to assign to self.tags, which is a property in this subclass.
684
# And the parent's __init__ doesn't do much anyway.
685
self._revision_history_cache = None
686
self.bzrdir = remote_bzrdir
687
if _client is not None:
688
self._client = _client
690
self._client = client._SmartClient(self.bzrdir._medium)
691
self.repository = remote_repository
692
if real_branch is not None:
693
self._real_branch = real_branch
694
# Give the remote repository the matching real repo.
695
real_repo = self._real_branch.repository
696
if isinstance(real_repo, RemoteRepository):
697
real_repo._ensure_real()
698
real_repo = real_repo._real_repository
699
self.repository._set_real_repository(real_repo)
700
# Give the branch the remote repository to let fast-pathing happen.
701
self._real_branch.repository = self.repository
703
self._real_branch = None
704
# Fill out expected attributes of branch for bzrlib api users.
705
self._format = RemoteBranchFormat()
706
self.base = self.bzrdir.root_transport.base
707
self._control_files = None
708
self._lock_mode = None
709
self._lock_token = None
711
self._leave_lock = False
713
def _ensure_real(self):
714
"""Ensure that there is a _real_branch set.
716
Used before calls to self._real_branch.
718
if not self._real_branch:
719
assert vfs.vfs_enabled()
720
self.bzrdir._ensure_real()
721
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
722
# Give the remote repository the matching real repo.
723
real_repo = self._real_branch.repository
724
if isinstance(real_repo, RemoteRepository):
725
real_repo._ensure_real()
726
real_repo = real_repo._real_repository
727
self.repository._set_real_repository(real_repo)
728
# Give the branch the remote repository to let fast-pathing happen.
729
self._real_branch.repository = self.repository
730
# XXX: deal with _lock_mode == 'w'
731
if self._lock_mode == 'r':
732
self._real_branch.lock_read()
735
def control_files(self):
736
# Defer actually creating RemoteBranchLockableFiles until its needed,
737
# because it triggers an _ensure_real that we otherwise might not need.
738
if self._control_files is None:
739
self._control_files = RemoteBranchLockableFiles(
740
self.bzrdir, self._client)
741
return self._control_files
743
def _get_checkout_format(self):
745
return self._real_branch._get_checkout_format()
747
def get_physical_lock_status(self):
748
"""See Branch.get_physical_lock_status()."""
749
# should be an API call to the server, as branches must be lockable.
751
return self._real_branch.get_physical_lock_status()
754
if not self._lock_mode:
755
self._lock_mode = 'r'
757
if self._real_branch is not None:
758
self._real_branch.lock_read()
760
self._lock_count += 1
762
def _remote_lock_write(self, token):
764
branch_token = repo_token = ''
767
repo_token = self.repository.lock_write()
768
self.repository.unlock()
769
path = self.bzrdir._path_for_remote_call(self._client)
770
response = self._client.call('Branch.lock_write', path, branch_token,
772
if response[0] == 'ok':
773
ok, branch_token, repo_token = response
774
return branch_token, repo_token
775
elif response[0] == 'LockContention':
776
raise errors.LockContention('(remote lock)')
777
elif response[0] == 'TokenMismatch':
778
raise errors.TokenMismatch(token, '(remote token)')
779
elif response[0] == 'UnlockableTransport':
780
raise errors.UnlockableTransport(self.bzrdir.root_transport)
781
elif response[0] == 'ReadOnlyError':
782
raise errors.ReadOnlyError(self)
784
assert False, 'unexpected response code %r' % (response,)
786
def lock_write(self, token=None):
787
if not self._lock_mode:
788
remote_tokens = self._remote_lock_write(token)
789
self._lock_token, self._repo_lock_token = remote_tokens
790
assert self._lock_token, 'Remote server did not return a token!'
791
# TODO: We really, really, really don't want to call _ensure_real
792
# here, but it's the easiest way to ensure coherency between the
793
# state of the RemoteBranch and RemoteRepository objects and the
794
# physical locks. If we don't materialise the real objects here,
795
# then getting everything in the right state later is complex, so
796
# for now we just do it the lazy way.
797
# -- Andrew Bennetts, 2007-02-22.
799
if self._real_branch is not None:
800
self._real_branch.repository.lock_write(
801
token=self._repo_lock_token)
803
self._real_branch.lock_write(token=self._lock_token)
805
self._real_branch.repository.unlock()
806
if token is not None:
807
self._leave_lock = True
809
# XXX: this case seems to be unreachable; token cannot be None.
810
self._leave_lock = False
811
self._lock_mode = 'w'
813
elif self._lock_mode == 'r':
814
raise errors.ReadOnlyTransaction
816
if token is not None:
817
# A token was given to lock_write, and we're relocking, so check
818
# that the given token actually matches the one we already have.
819
if token != self._lock_token:
820
raise errors.TokenMismatch(token, self._lock_token)
821
self._lock_count += 1
822
return self._lock_token
824
def _unlock(self, branch_token, repo_token):
825
path = self.bzrdir._path_for_remote_call(self._client)
826
response = self._client.call('Branch.unlock', path, branch_token,
828
if response == ('ok',):
830
elif response[0] == 'TokenMismatch':
831
raise errors.TokenMismatch(
832
str((branch_token, repo_token)), '(remote tokens)')
834
assert False, 'unexpected response code %s' % (response,)
837
self._lock_count -= 1
838
if not self._lock_count:
839
self._clear_cached_state()
840
mode = self._lock_mode
841
self._lock_mode = None
842
if self._real_branch is not None:
843
if not self._leave_lock:
844
# If this RemoteBranch will remove the physical lock for the
845
# repository, make sure the _real_branch doesn't do it
846
# first. (Because the _real_branch's repository is set to
847
# be the RemoteRepository.)
848
self._real_branch.repository.leave_lock_in_place()
849
self._real_branch.unlock()
851
# Only write-locked branched need to make a remote method call
852
# to perfom the unlock.
854
assert self._lock_token, 'Locked, but no token!'
855
branch_token = self._lock_token
856
repo_token = self._repo_lock_token
857
self._lock_token = None
858
self._repo_lock_token = None
859
if not self._leave_lock:
860
self._unlock(branch_token, repo_token)
862
def break_lock(self):
864
return self._real_branch.break_lock()
866
def leave_lock_in_place(self):
867
self._leave_lock = True
869
def dont_leave_lock_in_place(self):
870
self._leave_lock = False
872
def last_revision_info(self):
873
"""See Branch.last_revision_info()."""
874
path = self.bzrdir._path_for_remote_call(self._client)
875
response = self._client.call('Branch.last_revision_info', path)
876
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
877
revno = int(response[1])
878
last_revision = response[2]
879
return (revno, last_revision)
881
def _gen_revision_history(self):
882
"""See Branch._gen_revision_history()."""
883
path = self.bzrdir._path_for_remote_call(self._client)
884
response = self._client.call_expecting_body(
885
'Branch.revision_history', path)
886
assert response[0][0] == 'ok', ('unexpected response code %s'
888
result = response[1].read_body_bytes().split('\x00')
894
def set_revision_history(self, rev_history):
895
# Send just the tip revision of the history; the server will generate
896
# the full history from that. If the revision doesn't exist in this
897
# branch, NoSuchRevision will be raised.
898
path = self.bzrdir._path_for_remote_call(self._client)
899
if rev_history == []:
902
rev_id = rev_history[-1]
903
self._clear_cached_state()
904
response = self._client.call('Branch.set_last_revision',
905
path, self._lock_token, self._repo_lock_token, rev_id)
906
if response[0] == 'NoSuchRevision':
907
raise NoSuchRevision(self, rev_id)
909
assert response == ('ok',), (
910
'unexpected response code %r' % (response,))
911
self._cache_revision_history(rev_history)
913
def get_parent(self):
915
return self._real_branch.get_parent()
917
def set_parent(self, url):
919
return self._real_branch.set_parent(url)
921
def get_config(self):
922
return RemoteBranchConfig(self)
924
def sprout(self, to_bzrdir, revision_id=None):
925
# Like Branch.sprout, except that it sprouts a branch in the default
926
# format, because RemoteBranches can't be created at arbitrary URLs.
927
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
928
# to_bzrdir.create_branch...
930
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
931
self._real_branch.copy_content_into(result, revision_id=revision_id)
932
result.set_parent(self.bzrdir.root_transport.base)
936
def append_revision(self, *revision_ids):
938
return self._real_branch.append_revision(*revision_ids)
941
def pull(self, source, overwrite=False, stop_revision=None):
943
self._real_branch.pull(
944
source, overwrite=overwrite, stop_revision=stop_revision)
947
def push(self, target, overwrite=False, stop_revision=None):
949
return self._real_branch.push(
950
target, overwrite=overwrite, stop_revision=stop_revision)
953
return self._lock_count >= 1
955
def set_last_revision_info(self, revno, revision_id):
957
self._clear_cached_state()
958
return self._real_branch.set_last_revision_info(revno, revision_id)
960
def generate_revision_history(self, revision_id, last_rev=None,
963
return self._real_branch.generate_revision_history(
964
revision_id, last_rev=last_rev, other_branch=other_branch)
969
return self._real_branch.tags
971
def set_push_location(self, location):
973
return self._real_branch.set_push_location(location)
975
def update_revisions(self, other, stop_revision=None):
977
return self._real_branch.update_revisions(
978
other, stop_revision=stop_revision)
981
class RemoteBranchConfig(BranchConfig):
984
self.branch._ensure_real()
985
return self.branch._real_branch.get_config().username()
987
def _get_branch_data_config(self):
988
self.branch._ensure_real()
989
if self._branch_data_config is None:
990
self._branch_data_config = TreeConfig(self.branch._real_branch)
991
return self._branch_data_config