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
21
from urlparse import urlparse
23
from bzrlib import branch, errors, lockdir, repository
24
from bzrlib.branch import BranchReferenceFormat
25
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
26
from bzrlib.config import BranchConfig, TreeConfig
27
from bzrlib.decorators import needs_read_lock, needs_write_lock
28
from bzrlib.errors import NoSuchRevision
29
from bzrlib.lockable_files import LockableFiles
30
from bzrlib.revision import NULL_REVISION
31
from bzrlib.smart import client, vfs
32
from bzrlib.urlutils import unescape
34
# Note: RemoteBzrDirFormat is in bzrdir.py
36
class RemoteBzrDir(BzrDir):
37
"""Control directory on a remote server, accessed via bzr:// or similar."""
39
def __init__(self, transport, _client=None):
40
"""Construct a RemoteBzrDir.
42
:param _client: Private parameter for testing. Disables probing and the
45
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
46
# this object holds a delegated bzrdir that uses file-level operations
47
# to talk to the other side
48
self._real_bzrdir = None
51
self._medium = transport.get_smart_client()
52
self._client = client._SmartClient(self._medium)
54
self._client = _client
59
path = self._path_for_remote_call(self._client)
60
response = self._client.call('BzrDir.open', path)
61
if response not in [('yes',), ('no',)]:
62
raise errors.UnexpectedSmartServerResponse(response)
63
if response == ('no',):
64
raise errors.NotBranchError(path=transport.base)
66
def _ensure_real(self):
67
"""Ensure that there is a _real_bzrdir set.
69
Used before calls to self._real_bzrdir.
71
if not self._real_bzrdir:
72
# XXX: We can't use BzrDir.open_from_transport here because it
73
# causes infinite recursion, so just try opening the bzrdir with the
75
default_format = BzrDirFormat.get_default_format()
76
self._real_bzrdir = default_format.open(self.root_transport,
79
def create_repository(self, shared=False):
81
self._real_bzrdir.create_repository(shared=shared)
82
return self.open_repository()
84
def create_branch(self):
86
real_branch = self._real_bzrdir.create_branch()
87
return RemoteBranch(self, self.find_repository(), real_branch)
89
def create_workingtree(self, revision_id=None):
91
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
92
return RemoteWorkingTree(self, real_workingtree)
94
def find_branch_format(self):
95
"""Find the branch 'format' for this bzrdir.
97
This might be a synthetic object for e.g. RemoteBranch and SVN.
99
b = self.open_branch()
102
def get_branch_reference(self):
103
"""See BzrDir.get_branch_reference()."""
104
path = self._path_for_remote_call(self._client)
105
response = self._client.call('BzrDir.open_branch', path)
106
if response[0] == 'ok':
107
if response[1] == '':
108
# branch at this location.
111
# a branch reference, use the existing BranchReference logic.
113
elif response == ('nobranch',):
114
raise errors.NotBranchError(path=self.root_transport.base)
116
assert False, 'unexpected response code %r' % (response,)
118
def open_branch(self, _unsupported=False):
119
assert _unsupported == False, 'unsupported flag support not implemented yet.'
120
reference_url = self.get_branch_reference()
121
if reference_url is None:
122
# branch at this location.
123
return RemoteBranch(self, self.find_repository())
125
# a branch reference, use the existing BranchReference logic.
126
format = BranchReferenceFormat()
127
return format.open(self, _found=True, location=reference_url)
129
def open_repository(self):
130
path = self._path_for_remote_call(self._client)
131
response = self._client.call('BzrDir.find_repository', path)
132
assert response[0] in ('ok', 'norepository'), \
133
'unexpected response code %s' % (response,)
134
if response[0] == 'norepository':
135
raise errors.NoRepositoryPresent(self)
136
assert len(response) == 4, 'incorrect response length %s' % (response,)
137
if response[1] == '':
138
format = RemoteRepositoryFormat()
139
format.rich_root_data = (response[2] == 'yes')
140
format.supports_tree_reference = (response[3] == 'yes')
141
return RemoteRepository(self, format)
143
raise errors.NoRepositoryPresent(self)
145
def open_workingtree(self, recommend_upgrade=True):
146
raise errors.NotLocalUrl(self.root_transport)
148
def _path_for_remote_call(self, client):
149
"""Return the path to be used for this bzrdir in a remote call."""
150
return client.remote_path_from_transport(self.root_transport)
152
def get_branch_transport(self, branch_format):
154
return self._real_bzrdir.get_branch_transport(branch_format)
156
def get_repository_transport(self, repository_format):
158
return self._real_bzrdir.get_repository_transport(repository_format)
160
def get_workingtree_transport(self, workingtree_format):
162
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
164
def can_convert_format(self):
165
"""Upgrading of remote bzrdirs is not supported yet."""
168
def needs_format_conversion(self, format=None):
169
"""Upgrading of remote bzrdirs is not supported yet."""
172
def clone(self, url, revision_id=None, force_new_repo=False):
174
return self._real_bzrdir.clone(url, revision_id=revision_id,
175
force_new_repo=force_new_repo)
178
class RemoteRepositoryFormat(repository.RepositoryFormat):
179
"""Format for repositories accessed over a _SmartClient.
181
Instances of this repository are represented by RemoteRepository
184
The RemoteRepositoryFormat is parameterised during construction
185
to reflect the capabilities of the real, remote format. Specifically
186
the attributes rich_root_data and supports_tree_reference are set
187
on a per instance basis, and are not set (and should not be) at
191
_matchingbzrdir = RemoteBzrDirFormat
193
def initialize(self, a_bzrdir, shared=False):
194
assert isinstance(a_bzrdir, RemoteBzrDir), \
195
'%r is not a RemoteBzrDir' % (a_bzrdir,)
196
return a_bzrdir.create_repository(shared=shared)
198
def open(self, a_bzrdir):
199
assert isinstance(a_bzrdir, RemoteBzrDir)
200
return a_bzrdir.open_repository()
202
def get_format_description(self):
203
return 'bzr remote repository'
205
def __eq__(self, other):
206
return self.__class__ == other.__class__
208
def check_conversion_target(self, target_format):
209
if self.rich_root_data and not target_format.rich_root_data:
210
raise errors.BadConversionTarget(
211
'Does not support rich root data.', target_format)
212
if (self.supports_tree_reference and
213
not getattr(target_format, 'supports_tree_reference', False)):
214
raise errors.BadConversionTarget(
215
'Does not support nested trees', target_format)
218
class RemoteRepository(object):
219
"""Repository accessed over rpc.
221
For the moment most operations are performed using local transport-backed
225
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
226
"""Create a RemoteRepository instance.
228
:param remote_bzrdir: The bzrdir hosting this repository.
229
:param format: The RemoteFormat object to use.
230
:param real_repository: If not None, a local implementation of the
231
repository logic for the repository, usually accessing the data
233
:param _client: Private testing parameter - override the smart client
234
to be used by the repository.
237
self._real_repository = real_repository
239
self._real_repository = None
240
self.bzrdir = remote_bzrdir
242
self._client = client._SmartClient(self.bzrdir._medium)
244
self._client = _client
245
self._format = format
246
self._lock_mode = None
247
self._lock_token = None
249
self._leave_lock = False
251
def _ensure_real(self):
252
"""Ensure that there is a _real_repository set.
254
Used before calls to self._real_repository.
256
if not self._real_repository:
257
self.bzrdir._ensure_real()
258
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
259
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
261
def get_revision_graph(self, revision_id=None):
262
"""See Repository.get_revision_graph()."""
263
if revision_id is None:
265
elif revision_id == NULL_REVISION:
268
path = self.bzrdir._path_for_remote_call(self._client)
269
assert type(revision_id) is str
270
response = self._client.call_expecting_body(
271
'Repository.get_revision_graph', path, revision_id)
272
if response[0][0] not in ['ok', 'nosuchrevision']:
273
raise errors.UnexpectedSmartServerResponse(response[0])
274
if response[0][0] == 'ok':
275
coded = response[1].read_body_bytes()
277
# no revisions in this repository!
279
lines = coded.split('\n')
282
d = list(line.split())
283
revision_graph[d[0]] = d[1:]
285
return revision_graph
287
response_body = response[1].read_body_bytes()
288
assert response_body == ''
289
raise NoSuchRevision(self, revision_id)
291
def has_revision(self, revision_id):
292
"""See Repository.has_revision()."""
293
if revision_id is None:
294
# The null revision is always present.
296
path = self.bzrdir._path_for_remote_call(self._client)
297
response = self._client.call('Repository.has_revision', path, revision_id)
298
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
299
return response[0] == 'yes'
301
def gather_stats(self, revid=None, committers=None):
302
"""See Repository.gather_stats()."""
303
path = self.bzrdir._path_for_remote_call(self._client)
304
if revid in (None, NULL_REVISION):
308
if committers is None or not committers:
309
fmt_committers = 'no'
311
fmt_committers = 'yes'
312
response = self._client.call_expecting_body(
313
'Repository.gather_stats', path, fmt_revid, fmt_committers)
314
assert response[0][0] == 'ok', \
315
'unexpected response code %s' % (response[0],)
317
body = response[1].read_body_bytes()
319
for line in body.split('\n'):
322
key, val_text = line.split(':')
323
if key in ('revisions', 'size', 'committers'):
324
result[key] = int(val_text)
325
elif key in ('firstrev', 'latestrev'):
326
values = val_text.split(' ')[1:]
327
result[key] = (float(values[0]), long(values[1]))
331
def get_physical_lock_status(self):
332
"""See Repository.get_physical_lock_status()."""
336
"""See Repository.is_shared()."""
337
path = self.bzrdir._path_for_remote_call(self._client)
338
response = self._client.call('Repository.is_shared', path)
339
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
340
return response[0] == 'yes'
343
# wrong eventually - want a local lock cache context
344
if not self._lock_mode:
345
self._lock_mode = 'r'
347
if self._real_repository is not None:
348
self._real_repository.lock_read()
350
self._lock_count += 1
352
def _remote_lock_write(self, token):
353
path = self.bzrdir._path_for_remote_call(self._client)
356
response = self._client.call('Repository.lock_write', path, token)
357
if response[0] == 'ok':
360
elif response[0] == 'LockContention':
361
raise errors.LockContention('(remote lock)')
362
elif response[0] == 'UnlockableTransport':
363
raise errors.UnlockableTransport(self.bzrdir.root_transport)
365
assert False, 'unexpected response code %s' % (response,)
367
def lock_write(self, token=None):
368
if not self._lock_mode:
369
self._lock_token = self._remote_lock_write(token)
370
assert self._lock_token, 'Remote server did not return a token!'
371
if self._real_repository is not None:
372
self._real_repository.lock_write(token=self._lock_token)
373
if token is not None:
374
self._leave_lock = True
376
self._leave_lock = False
377
self._lock_mode = 'w'
379
elif self._lock_mode == 'r':
380
raise errors.ReadOnlyError(self)
382
self._lock_count += 1
383
return self._lock_token
385
def leave_lock_in_place(self):
386
self._leave_lock = True
388
def dont_leave_lock_in_place(self):
389
self._leave_lock = False
391
def _set_real_repository(self, repository):
392
"""Set the _real_repository for this repository.
394
:param repository: The repository to fallback to for non-hpss
395
implemented operations.
397
assert not isinstance(repository, RemoteRepository)
398
self._real_repository = repository
399
if self._lock_mode == 'w':
400
# if we are already locked, the real repository must be able to
401
# acquire the lock with our token.
402
self._real_repository.lock_write(self._lock_token)
403
elif self._lock_mode == 'r':
404
self._real_repository.lock_read()
406
def _unlock(self, token):
407
path = self.bzrdir._path_for_remote_call(self._client)
408
response = self._client.call('Repository.unlock', path, token)
409
if response == ('ok',):
411
elif response[0] == 'TokenMismatch':
412
raise errors.TokenMismatch(token, '(remote token)')
414
assert False, 'unexpected response code %s' % (response,)
417
self._lock_count -= 1
418
if not self._lock_count:
419
mode = self._lock_mode
420
self._lock_mode = None
421
if self._real_repository is not None:
422
self._real_repository.unlock()
424
# Only write-locked repositories need to make a remote method
425
# call to perfom the unlock.
427
assert self._lock_token, 'Locked, but no token!'
428
token = self._lock_token
429
self._lock_token = None
430
if not self._leave_lock:
433
def break_lock(self):
434
# should hand off to the network
436
return self._real_repository.break_lock()
438
### These methods are just thin shims to the VFS object for now.
440
def revision_tree(self, revision_id):
442
return self._real_repository.revision_tree(revision_id)
444
def get_commit_builder(self, branch, parents, config, timestamp=None,
445
timezone=None, committer=None, revprops=None,
447
# FIXME: It ought to be possible to call this without immediately
448
# triggering _ensure_real. For now it's the easiest thing to do.
450
builder = self._real_repository.get_commit_builder(branch, parents,
451
config, timestamp=timestamp, timezone=timezone,
452
committer=committer, revprops=revprops, revision_id=revision_id)
453
# Make the builder use this RemoteRepository rather than the real one.
454
builder.repository = self
458
def add_inventory(self, revid, inv, parents):
460
return self._real_repository.add_inventory(revid, inv, parents)
463
def add_revision(self, rev_id, rev, inv=None, config=None):
465
return self._real_repository.add_revision(
466
rev_id, rev, inv=inv, config=config)
469
def get_inventory(self, revision_id):
471
return self._real_repository.get_inventory(revision_id)
474
def get_revision(self, revision_id):
476
return self._real_repository.get_revision(revision_id)
479
def weave_store(self):
481
return self._real_repository.weave_store
483
def get_transaction(self):
485
return self._real_repository.get_transaction()
488
def clone(self, a_bzrdir, revision_id=None):
490
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
492
def make_working_trees(self):
493
"""RemoteRepositories never create working trees by default."""
496
def fetch(self, source, revision_id=None, pb=None):
498
return self._real_repository.fetch(
499
source, revision_id=revision_id, pb=pb)
502
def control_weaves(self):
504
return self._real_repository.control_weaves
507
def get_ancestry(self, revision_id):
509
return self._real_repository.get_ancestry(revision_id)
512
def get_inventory_weave(self):
514
return self._real_repository.get_inventory_weave()
516
def fileids_altered_by_revision_ids(self, revision_ids):
518
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
521
def get_signature_text(self, revision_id):
523
return self._real_repository.get_signature_text(revision_id)
526
def get_revision_graph_with_ghosts(self, revision_ids=None):
528
return self._real_repository.get_revision_graph_with_ghosts(
529
revision_ids=revision_ids)
532
def get_inventory_xml(self, revision_id):
534
return self._real_repository.get_inventory_xml(revision_id)
536
def deserialise_inventory(self, revision_id, xml):
538
return self._real_repository.deserialise_inventory(revision_id, xml)
540
def reconcile(self, other=None, thorough=False):
542
return self._real_repository.reconcile(other=other, thorough=thorough)
544
def all_revision_ids(self):
546
return self._real_repository.all_revision_ids()
549
def get_deltas_for_revisions(self, revisions):
551
return self._real_repository.get_deltas_for_revisions(revisions)
554
def get_revision_delta(self, revision_id):
556
return self._real_repository.get_revision_delta(revision_id)
559
def revision_trees(self, revision_ids):
561
return self._real_repository.revision_trees(revision_ids)
564
def get_revision_reconcile(self, revision_id):
566
return self._real_repository.get_revision_reconcile(revision_id)
569
def check(self, revision_ids):
571
return self._real_repository.check(revision_ids)
573
def copy_content_into(self, destination, revision_id=None):
575
return self._real_repository.copy_content_into(
576
destination, revision_id=revision_id)
578
def set_make_working_trees(self, new_value):
579
raise NotImplementedError(self.set_make_working_trees)
582
def sign_revision(self, revision_id, gpg_strategy):
584
return self._real_repository.sign_revision(revision_id, gpg_strategy)
587
def get_revisions(self, revision_ids):
589
return self._real_repository.get_revisions(revision_ids)
591
def supports_rich_root(self):
593
return self._real_repository.supports_rich_root()
595
def iter_reverse_revision_history(self, revision_id):
597
return self._real_repository.iter_reverse_revision_history(revision_id)
600
def _serializer(self):
602
return self._real_repository._serializer
604
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
606
return self._real_repository.store_revision_signature(
607
gpg_strategy, plaintext, revision_id)
609
def has_signature_for_revision_id(self, revision_id):
611
return self._real_repository.has_signature_for_revision_id(revision_id)
614
class RemoteBranchLockableFiles(LockableFiles):
615
"""A 'LockableFiles' implementation that talks to a smart server.
617
This is not a public interface class.
620
def __init__(self, bzrdir, _client):
622
self._client = _client
623
self._need_find_modes = True
624
LockableFiles.__init__(
625
self, bzrdir.get_branch_transport(None),
626
'lock', lockdir.LockDir)
628
def _find_modes(self):
629
# RemoteBranches don't let the client set the mode of control files.
630
self._dir_mode = None
631
self._file_mode = None
634
"""'get' a remote path as per the LockableFiles interface.
636
:param path: the file to 'get'. If this is 'branch.conf', we do not
637
just retrieve a file, instead we ask the smart server to generate
638
a configuration for us - which is retrieved as an INI file.
640
if path == 'branch.conf':
641
path = self.bzrdir._path_for_remote_call(self._client)
642
response = self._client.call_expecting_body(
643
'Branch.get_config_file', path)
644
assert response[0][0] == 'ok', \
645
'unexpected response code %s' % (response[0],)
646
return StringIO(response[1].read_body_bytes())
649
return LockableFiles.get(self, path)
652
class RemoteBranchFormat(branch.BranchFormat):
654
def __eq__(self, other):
655
return (isinstance(other, RemoteBranchFormat) and
656
self.__dict__ == other.__dict__)
658
def get_format_description(self):
659
return 'Remote BZR Branch'
661
def get_format_string(self):
662
return 'Remote BZR Branch'
664
def open(self, a_bzrdir):
665
assert isinstance(a_bzrdir, RemoteBzrDir)
666
return a_bzrdir.open_branch()
668
def initialize(self, a_bzrdir):
669
assert isinstance(a_bzrdir, RemoteBzrDir)
670
return a_bzrdir.create_branch()
673
class RemoteBranch(branch.Branch):
674
"""Branch stored on a server accessed by HPSS RPC.
676
At the moment most operations are mapped down to simple file operations.
679
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
681
"""Create a RemoteBranch instance.
683
:param real_branch: An optional local implementation of the branch
684
format, usually accessing the data via the VFS.
685
:param _client: Private parameter for testing.
687
# We intentionally don't call the parent class's __init__, because it
688
# will try to assign to self.tags, which is a property in this subclass.
689
# And the parent's __init__ doesn't do much anyway.
690
self._revision_history_cache = None
691
self.bzrdir = remote_bzrdir
692
if _client is not None:
693
self._client = _client
695
self._client = client._SmartClient(self.bzrdir._medium)
696
self.repository = remote_repository
697
if real_branch is not None:
698
self._real_branch = real_branch
699
# Give the remote repository the matching real repo.
700
real_repo = self._real_branch.repository
701
if isinstance(real_repo, RemoteRepository):
702
real_repo._ensure_real()
703
real_repo = real_repo._real_repository
704
self.repository._set_real_repository(real_repo)
705
# Give the branch the remote repository to let fast-pathing happen.
706
self._real_branch.repository = self.repository
708
self._real_branch = None
709
# Fill out expected attributes of branch for bzrlib api users.
710
self._format = RemoteBranchFormat()
711
self.base = self.bzrdir.root_transport.base
712
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
713
self._lock_mode = None
714
self._lock_token = None
716
self._leave_lock = False
718
def _ensure_real(self):
719
"""Ensure that there is a _real_branch set.
721
Used before calls to self._real_branch.
723
if not self._real_branch:
724
assert vfs.vfs_enabled()
725
self.bzrdir._ensure_real()
726
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
727
# Give the remote repository the matching real repo.
728
real_repo = self._real_branch.repository
729
if isinstance(real_repo, RemoteRepository):
730
real_repo._ensure_real()
731
real_repo = real_repo._real_repository
732
self.repository._set_real_repository(real_repo)
733
# Give the branch the remote repository to let fast-pathing happen.
734
self._real_branch.repository = self.repository
735
# XXX: deal with _lock_mode == 'w'
736
if self._lock_mode == 'r':
737
self._real_branch.lock_read()
739
def _get_checkout_format(self):
741
return self._real_branch._get_checkout_format()
743
def get_physical_lock_status(self):
744
"""See Branch.get_physical_lock_status()."""
745
# should be an API call to the server, as branches must be lockable.
747
return self._real_branch.get_physical_lock_status()
750
if not self._lock_mode:
751
self._lock_mode = 'r'
753
if self._real_branch is not None:
754
self._real_branch.lock_read()
756
self._lock_count += 1
758
def _remote_lock_write(self, token):
760
branch_token = repo_token = ''
763
repo_token = self.repository.lock_write()
764
self.repository.unlock()
765
path = self.bzrdir._path_for_remote_call(self._client)
766
response = self._client.call('Branch.lock_write', path, branch_token,
768
if response[0] == 'ok':
769
ok, branch_token, repo_token = response
770
return branch_token, repo_token
771
elif response[0] == 'LockContention':
772
raise errors.LockContention('(remote lock)')
773
elif response[0] == 'TokenMismatch':
774
raise errors.TokenMismatch(token, '(remote token)')
775
elif response[0] == 'UnlockableTransport':
776
raise errors.UnlockableTransport(self.bzrdir.root_transport)
777
elif response[0] == 'ReadOnlyError':
778
raise errors.ReadOnlyError(self)
780
assert False, 'unexpected response code %r' % (response,)
782
def lock_write(self, token=None):
783
if not self._lock_mode:
784
remote_tokens = self._remote_lock_write(token)
785
self._lock_token, self._repo_lock_token = remote_tokens
786
assert self._lock_token, 'Remote server did not return a token!'
787
# TODO: We really, really, really don't want to call _ensure_real
788
# here, but it's the easiest way to ensure coherency between the
789
# state of the RemoteBranch and RemoteRepository objects and the
790
# physical locks. If we don't materialise the real objects here,
791
# then getting everything in the right state later is complex, so
792
# for now we just do it the lazy way.
793
# -- Andrew Bennetts, 2007-02-22.
795
if self._real_branch is not None:
796
self._real_branch.repository.lock_write(
797
token=self._repo_lock_token)
799
self._real_branch.lock_write(token=self._lock_token)
801
self._real_branch.repository.unlock()
802
if token is not None:
803
self._leave_lock = True
805
# XXX: this case seems to be unreachable; token cannot be None.
806
self._leave_lock = False
807
self._lock_mode = 'w'
809
elif self._lock_mode == 'r':
810
raise errors.ReadOnlyTransaction
812
if token is not None:
813
# A token was given to lock_write, and we're relocking, so check
814
# that the given token actually matches the one we already have.
815
if token != self._lock_token:
816
raise errors.TokenMismatch(token, self._lock_token)
817
self._lock_count += 1
818
return self._lock_token
820
def _unlock(self, branch_token, repo_token):
821
path = self.bzrdir._path_for_remote_call(self._client)
822
response = self._client.call('Branch.unlock', path, branch_token,
824
if response == ('ok',):
826
elif response[0] == 'TokenMismatch':
827
raise errors.TokenMismatch(
828
str((branch_token, repo_token)), '(remote tokens)')
830
assert False, 'unexpected response code %s' % (response,)
833
self._lock_count -= 1
834
if not self._lock_count:
835
self._clear_cached_state()
836
mode = self._lock_mode
837
self._lock_mode = None
838
if self._real_branch is not None:
839
if not self._leave_lock:
840
# If this RemoteBranch will remove the physical lock for the
841
# repository, make sure the _real_branch doesn't do it
842
# first. (Because the _real_branch's repository is set to
843
# be the RemoteRepository.)
844
self._real_branch.repository.leave_lock_in_place()
845
self._real_branch.unlock()
847
# Only write-locked branched need to make a remote method call
848
# to perfom the unlock.
850
assert self._lock_token, 'Locked, but no token!'
851
branch_token = self._lock_token
852
repo_token = self._repo_lock_token
853
self._lock_token = None
854
self._repo_lock_token = None
855
if not self._leave_lock:
856
self._unlock(branch_token, repo_token)
858
def break_lock(self):
860
return self._real_branch.break_lock()
862
def leave_lock_in_place(self):
863
self._leave_lock = True
865
def dont_leave_lock_in_place(self):
866
self._leave_lock = False
868
def last_revision_info(self):
869
"""See Branch.last_revision_info()."""
870
path = self.bzrdir._path_for_remote_call(self._client)
871
response = self._client.call('Branch.last_revision_info', path)
872
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
873
revno = int(response[1])
874
last_revision = response[2]
875
if last_revision == '':
876
last_revision = NULL_REVISION
877
return (revno, last_revision)
879
def _gen_revision_history(self):
880
"""See Branch._gen_revision_history()."""
881
path = self.bzrdir._path_for_remote_call(self._client)
882
response = self._client.call_expecting_body(
883
'Branch.revision_history', path)
884
assert response[0][0] == 'ok', ('unexpected response code %s'
886
result = response[1].read_body_bytes().split('\x00')
892
def set_revision_history(self, rev_history):
893
# Send just the tip revision of the history; the server will generate
894
# the full history from that. If the revision doesn't exist in this
895
# branch, NoSuchRevision will be raised.
896
path = self.bzrdir._path_for_remote_call(self._client)
897
if rev_history == []:
900
rev_id = rev_history[-1]
901
response = self._client.call('Branch.set_last_revision',
902
path, self._lock_token, self._repo_lock_token, rev_id)
903
if response[0] == 'NoSuchRevision':
904
raise NoSuchRevision(self, rev_id)
906
assert response == ('ok',), (
907
'unexpected response code %r' % (response,))
908
self._cache_revision_history(rev_history)
910
def get_parent(self):
912
return self._real_branch.get_parent()
914
def set_parent(self, url):
916
return self._real_branch.set_parent(url)
918
def get_config(self):
919
return RemoteBranchConfig(self)
921
def sprout(self, to_bzrdir, revision_id=None):
922
# Like Branch.sprout, except that it sprouts a branch in the default
923
# format, because RemoteBranches can't be created at arbitrary URLs.
924
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
925
# to_bzrdir.create_branch...
927
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
928
self._real_branch.copy_content_into(result, revision_id=revision_id)
929
result.set_parent(self.bzrdir.root_transport.base)
933
def append_revision(self, *revision_ids):
935
return self._real_branch.append_revision(*revision_ids)
938
def pull(self, source, overwrite=False, stop_revision=None):
940
self._real_branch.pull(
941
source, overwrite=overwrite, stop_revision=stop_revision)
944
def push(self, target, overwrite=False, stop_revision=None):
946
return self._real_branch.push(
947
target, overwrite=overwrite, stop_revision=stop_revision)
950
return self._lock_count >= 1
952
def set_last_revision_info(self, revno, revision_id):
954
self._clear_cached_state()
955
return self._real_branch.set_last_revision_info(revno, revision_id)
957
def generate_revision_history(self, revision_id, last_rev=None,
960
return self._real_branch.generate_revision_history(
961
revision_id, last_rev=last_rev, other_branch=other_branch)
966
return self._real_branch.tags
968
def set_push_location(self, location):
970
return self._real_branch.set_push_location(location)
972
def update_revisions(self, other, stop_revision=None):
974
return self._real_branch.update_revisions(
975
other, stop_revision=stop_revision)
978
class RemoteBranchConfig(BranchConfig):
981
self.branch._ensure_real()
982
return self.branch._real_branch.get_config().username()
984
def _get_branch_data_config(self):
985
self.branch._ensure_real()
986
if self._branch_data_config is None:
987
self._branch_data_config = TreeConfig(self.branch._real_branch)
988
return self._branch_data_config