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
def sprout(self, to_bzrdir, revision_id=None):
431
# TODO: Option to control what format is created?
432
to_repo = to_bzrdir.create_repository()
433
to_repo.fetch(self, revision_id=revision_id)
436
### These methods are just thin shims to the VFS object for now.
438
def revision_tree(self, revision_id):
440
return self._real_repository.revision_tree(revision_id)
442
def get_commit_builder(self, branch, parents, config, timestamp=None,
443
timezone=None, committer=None, revprops=None,
445
# FIXME: It ought to be possible to call this without immediately
446
# triggering _ensure_real. For now it's the easiest thing to do.
448
builder = self._real_repository.get_commit_builder(branch, parents,
449
config, timestamp=timestamp, timezone=timezone,
450
committer=committer, revprops=revprops, revision_id=revision_id)
451
# Make the builder use this RemoteRepository rather than the real one.
452
builder.repository = self
456
def add_inventory(self, revid, inv, parents):
458
return self._real_repository.add_inventory(revid, inv, parents)
461
def add_revision(self, rev_id, rev, inv=None, config=None):
463
return self._real_repository.add_revision(
464
rev_id, rev, inv=inv, config=config)
467
def get_inventory(self, revision_id):
469
return self._real_repository.get_inventory(revision_id)
472
def get_revision(self, revision_id):
474
return self._real_repository.get_revision(revision_id)
477
def weave_store(self):
479
return self._real_repository.weave_store
481
def get_transaction(self):
483
return self._real_repository.get_transaction()
486
def clone(self, a_bzrdir, revision_id=None):
488
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
490
def make_working_trees(self):
491
"""RemoteRepositories never create working trees by default."""
494
def fetch(self, source, revision_id=None, pb=None):
496
return self._real_repository.fetch(
497
source, revision_id=revision_id, pb=pb)
500
def control_weaves(self):
502
return self._real_repository.control_weaves
505
def get_ancestry(self, revision_id):
507
return self._real_repository.get_ancestry(revision_id)
510
def get_inventory_weave(self):
512
return self._real_repository.get_inventory_weave()
514
def fileids_altered_by_revision_ids(self, revision_ids):
516
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
519
def get_signature_text(self, revision_id):
521
return self._real_repository.get_signature_text(revision_id)
524
def get_revision_graph_with_ghosts(self, revision_ids=None):
526
return self._real_repository.get_revision_graph_with_ghosts(
527
revision_ids=revision_ids)
530
def get_inventory_xml(self, revision_id):
532
return self._real_repository.get_inventory_xml(revision_id)
534
def deserialise_inventory(self, revision_id, xml):
536
return self._real_repository.deserialise_inventory(revision_id, xml)
538
def reconcile(self, other=None, thorough=False):
540
return self._real_repository.reconcile(other=other, thorough=thorough)
542
def all_revision_ids(self):
544
return self._real_repository.all_revision_ids()
547
def get_deltas_for_revisions(self, revisions):
549
return self._real_repository.get_deltas_for_revisions(revisions)
552
def get_revision_delta(self, revision_id):
554
return self._real_repository.get_revision_delta(revision_id)
557
def revision_trees(self, revision_ids):
559
return self._real_repository.revision_trees(revision_ids)
562
def get_revision_reconcile(self, revision_id):
564
return self._real_repository.get_revision_reconcile(revision_id)
567
def check(self, revision_ids):
569
return self._real_repository.check(revision_ids)
571
def copy_content_into(self, destination, revision_id=None):
573
return self._real_repository.copy_content_into(
574
destination, revision_id=revision_id)
576
def set_make_working_trees(self, new_value):
577
raise NotImplementedError(self.set_make_working_trees)
580
def sign_revision(self, revision_id, gpg_strategy):
582
return self._real_repository.sign_revision(revision_id, gpg_strategy)
585
def get_revisions(self, revision_ids):
587
return self._real_repository.get_revisions(revision_ids)
589
def supports_rich_root(self):
591
return self._real_repository.supports_rich_root()
593
def iter_reverse_revision_history(self, revision_id):
595
return self._real_repository.iter_reverse_revision_history(revision_id)
598
def _serializer(self):
600
return self._real_repository._serializer
602
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
604
return self._real_repository.store_revision_signature(
605
gpg_strategy, plaintext, revision_id)
607
def has_signature_for_revision_id(self, revision_id):
609
return self._real_repository.has_signature_for_revision_id(revision_id)
612
class RemoteBranchLockableFiles(LockableFiles):
613
"""A 'LockableFiles' implementation that talks to a smart server.
615
This is not a public interface class.
618
def __init__(self, bzrdir, _client):
620
self._client = _client
621
self._need_find_modes = True
622
LockableFiles.__init__(
623
self, bzrdir.get_branch_transport(None),
624
'lock', lockdir.LockDir)
626
def _find_modes(self):
627
# RemoteBranches don't let the client set the mode of control files.
628
self._dir_mode = None
629
self._file_mode = None
632
"""'get' a remote path as per the LockableFiles interface.
634
:param path: the file to 'get'. If this is 'branch.conf', we do not
635
just retrieve a file, instead we ask the smart server to generate
636
a configuration for us - which is retrieved as an INI file.
638
if path == 'branch.conf':
639
path = self.bzrdir._path_for_remote_call(self._client)
640
response = self._client.call_expecting_body(
641
'Branch.get_config_file', path)
642
assert response[0][0] == 'ok', \
643
'unexpected response code %s' % (response[0],)
644
return StringIO(response[1].read_body_bytes())
647
return LockableFiles.get(self, path)
650
class RemoteBranchFormat(branch.BranchFormat):
652
def __eq__(self, other):
653
return (isinstance(other, RemoteBranchFormat) and
654
self.__dict__ == other.__dict__)
656
def get_format_description(self):
657
return 'Remote BZR Branch'
659
def get_format_string(self):
660
return 'Remote BZR Branch'
662
def open(self, a_bzrdir):
663
assert isinstance(a_bzrdir, RemoteBzrDir)
664
return a_bzrdir.open_branch()
666
def initialize(self, a_bzrdir):
667
assert isinstance(a_bzrdir, RemoteBzrDir)
668
return a_bzrdir.create_branch()
671
class RemoteBranch(branch.Branch):
672
"""Branch stored on a server accessed by HPSS RPC.
674
At the moment most operations are mapped down to simple file operations.
677
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
679
"""Create a RemoteBranch instance.
681
:param real_branch: An optional local implementation of the branch
682
format, usually accessing the data via the VFS.
683
:param _client: Private parameter for testing.
685
# We intentionally don't call the parent class's __init__, because it
686
# will try to assign to self.tags, which is a property in this subclass.
687
# And the parent's __init__ doesn't do much anyway.
688
self._revision_history_cache = None
689
self.bzrdir = remote_bzrdir
690
if _client is not None:
691
self._client = _client
693
self._client = client._SmartClient(self.bzrdir._medium)
694
self.repository = remote_repository
695
if real_branch is not None:
696
self._real_branch = real_branch
697
# Give the remote repository the matching real repo.
698
real_repo = self._real_branch.repository
699
if isinstance(real_repo, RemoteRepository):
700
real_repo._ensure_real()
701
real_repo = real_repo._real_repository
702
self.repository._set_real_repository(real_repo)
703
# Give the branch the remote repository to let fast-pathing happen.
704
self._real_branch.repository = self.repository
706
self._real_branch = None
707
# Fill out expected attributes of branch for bzrlib api users.
708
self._format = RemoteBranchFormat()
709
self.base = self.bzrdir.root_transport.base
710
self._control_files = None
711
self._lock_mode = None
712
self._lock_token = None
714
self._leave_lock = False
716
def _ensure_real(self):
717
"""Ensure that there is a _real_branch set.
719
Used before calls to self._real_branch.
721
if not self._real_branch:
722
assert vfs.vfs_enabled()
723
self.bzrdir._ensure_real()
724
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
725
# Give the remote repository the matching real repo.
726
real_repo = self._real_branch.repository
727
if isinstance(real_repo, RemoteRepository):
728
real_repo._ensure_real()
729
real_repo = real_repo._real_repository
730
self.repository._set_real_repository(real_repo)
731
# Give the branch the remote repository to let fast-pathing happen.
732
self._real_branch.repository = self.repository
733
# XXX: deal with _lock_mode == 'w'
734
if self._lock_mode == 'r':
735
self._real_branch.lock_read()
738
def control_files(self):
739
# Defer actually creating RemoteBranchLockableFiles until its needed,
740
# because it triggers an _ensure_real that we otherwise might not need.
741
if self._control_files is None:
742
self._control_files = RemoteBranchLockableFiles(
743
self.bzrdir, self._client)
744
return self._control_files
746
def _get_checkout_format(self):
748
return self._real_branch._get_checkout_format()
750
def get_physical_lock_status(self):
751
"""See Branch.get_physical_lock_status()."""
752
# should be an API call to the server, as branches must be lockable.
754
return self._real_branch.get_physical_lock_status()
757
if not self._lock_mode:
758
self._lock_mode = 'r'
760
if self._real_branch is not None:
761
self._real_branch.lock_read()
763
self._lock_count += 1
765
def _remote_lock_write(self, token):
767
branch_token = repo_token = ''
770
repo_token = self.repository.lock_write()
771
self.repository.unlock()
772
path = self.bzrdir._path_for_remote_call(self._client)
773
response = self._client.call('Branch.lock_write', path, branch_token,
775
if response[0] == 'ok':
776
ok, branch_token, repo_token = response
777
return branch_token, repo_token
778
elif response[0] == 'LockContention':
779
raise errors.LockContention('(remote lock)')
780
elif response[0] == 'TokenMismatch':
781
raise errors.TokenMismatch(token, '(remote token)')
782
elif response[0] == 'UnlockableTransport':
783
raise errors.UnlockableTransport(self.bzrdir.root_transport)
784
elif response[0] == 'ReadOnlyError':
785
raise errors.ReadOnlyError(self)
787
assert False, 'unexpected response code %r' % (response,)
789
def lock_write(self, token=None):
790
if not self._lock_mode:
791
remote_tokens = self._remote_lock_write(token)
792
self._lock_token, self._repo_lock_token = remote_tokens
793
assert self._lock_token, 'Remote server did not return a token!'
794
# TODO: We really, really, really don't want to call _ensure_real
795
# here, but it's the easiest way to ensure coherency between the
796
# state of the RemoteBranch and RemoteRepository objects and the
797
# physical locks. If we don't materialise the real objects here,
798
# then getting everything in the right state later is complex, so
799
# for now we just do it the lazy way.
800
# -- Andrew Bennetts, 2007-02-22.
802
if self._real_branch is not None:
803
self._real_branch.repository.lock_write(
804
token=self._repo_lock_token)
806
self._real_branch.lock_write(token=self._lock_token)
808
self._real_branch.repository.unlock()
809
if token is not None:
810
self._leave_lock = True
812
# XXX: this case seems to be unreachable; token cannot be None.
813
self._leave_lock = False
814
self._lock_mode = 'w'
816
elif self._lock_mode == 'r':
817
raise errors.ReadOnlyTransaction
819
if token is not None:
820
# A token was given to lock_write, and we're relocking, so check
821
# that the given token actually matches the one we already have.
822
if token != self._lock_token:
823
raise errors.TokenMismatch(token, self._lock_token)
824
self._lock_count += 1
825
return self._lock_token
827
def _unlock(self, branch_token, repo_token):
828
path = self.bzrdir._path_for_remote_call(self._client)
829
response = self._client.call('Branch.unlock', path, branch_token,
831
if response == ('ok',):
833
elif response[0] == 'TokenMismatch':
834
raise errors.TokenMismatch(
835
str((branch_token, repo_token)), '(remote tokens)')
837
assert False, 'unexpected response code %s' % (response,)
840
self._lock_count -= 1
841
if not self._lock_count:
842
self._clear_cached_state()
843
mode = self._lock_mode
844
self._lock_mode = None
845
if self._real_branch is not None:
846
if not self._leave_lock:
847
# If this RemoteBranch will remove the physical lock for the
848
# repository, make sure the _real_branch doesn't do it
849
# first. (Because the _real_branch's repository is set to
850
# be the RemoteRepository.)
851
self._real_branch.repository.leave_lock_in_place()
852
self._real_branch.unlock()
854
# Only write-locked branched need to make a remote method call
855
# to perfom the unlock.
857
assert self._lock_token, 'Locked, but no token!'
858
branch_token = self._lock_token
859
repo_token = self._repo_lock_token
860
self._lock_token = None
861
self._repo_lock_token = None
862
if not self._leave_lock:
863
self._unlock(branch_token, repo_token)
865
def break_lock(self):
867
return self._real_branch.break_lock()
869
def leave_lock_in_place(self):
870
self._leave_lock = True
872
def dont_leave_lock_in_place(self):
873
self._leave_lock = False
875
def last_revision_info(self):
876
"""See Branch.last_revision_info()."""
877
path = self.bzrdir._path_for_remote_call(self._client)
878
response = self._client.call('Branch.last_revision_info', path)
879
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
880
revno = int(response[1])
881
last_revision = response[2]
882
return (revno, last_revision)
884
def _gen_revision_history(self):
885
"""See Branch._gen_revision_history()."""
886
path = self.bzrdir._path_for_remote_call(self._client)
887
response = self._client.call_expecting_body(
888
'Branch.revision_history', path)
889
assert response[0][0] == 'ok', ('unexpected response code %s'
891
result = response[1].read_body_bytes().split('\x00')
897
def set_revision_history(self, rev_history):
898
# Send just the tip revision of the history; the server will generate
899
# the full history from that. If the revision doesn't exist in this
900
# branch, NoSuchRevision will be raised.
901
path = self.bzrdir._path_for_remote_call(self._client)
902
if rev_history == []:
905
rev_id = rev_history[-1]
906
response = self._client.call('Branch.set_last_revision',
907
path, self._lock_token, self._repo_lock_token, rev_id)
908
if response[0] == 'NoSuchRevision':
909
raise NoSuchRevision(self, rev_id)
911
assert response == ('ok',), (
912
'unexpected response code %r' % (response,))
913
self._cache_revision_history(rev_history)
915
def get_parent(self):
917
return self._real_branch.get_parent()
919
def set_parent(self, url):
921
return self._real_branch.set_parent(url)
923
def get_config(self):
924
return RemoteBranchConfig(self)
926
def sprout(self, to_bzrdir, revision_id=None):
927
# Like Branch.sprout, except that it sprouts a branch in the default
928
# format, because RemoteBranches can't be created at arbitrary URLs.
929
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
930
# to_bzrdir.create_branch...
932
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
933
self._real_branch.copy_content_into(result, revision_id=revision_id)
934
result.set_parent(self.bzrdir.root_transport.base)
938
def append_revision(self, *revision_ids):
940
return self._real_branch.append_revision(*revision_ids)
943
def pull(self, source, overwrite=False, stop_revision=None):
945
self._real_branch.pull(
946
source, overwrite=overwrite, stop_revision=stop_revision)
949
def push(self, target, overwrite=False, stop_revision=None):
951
return self._real_branch.push(
952
target, overwrite=overwrite, stop_revision=stop_revision)
955
return self._lock_count >= 1
957
def set_last_revision_info(self, revno, revision_id):
959
self._clear_cached_state()
960
return self._real_branch.set_last_revision_info(revno, revision_id)
962
def generate_revision_history(self, revision_id, last_rev=None,
965
return self._real_branch.generate_revision_history(
966
revision_id, last_rev=last_rev, other_branch=other_branch)
971
return self._real_branch.tags
973
def set_push_location(self, location):
975
return self._real_branch.set_push_location(location)
977
def update_revisions(self, other, stop_revision=None):
979
return self._real_branch.update_revisions(
980
other, stop_revision=stop_revision)
983
class RemoteBranchConfig(BranchConfig):
986
self.branch._ensure_real()
987
return self.branch._real_branch.get_config().username()
989
def _get_branch_data_config(self):
990
self.branch._ensure_real()
991
if self._branch_data_config is None:
992
self._branch_data_config = TreeConfig(self.branch._real_branch)
993
return self._branch_data_config