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] == 'True'
140
format.supports_tree_reference = response[3] == 'True'
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_physical_lock_status(self):
740
"""See Branch.get_physical_lock_status()."""
741
# should be an API call to the server, as branches must be lockable.
743
return self._real_branch.get_physical_lock_status()
746
if not self._lock_mode:
747
self._lock_mode = 'r'
749
if self._real_branch is not None:
750
self._real_branch.lock_read()
752
self._lock_count += 1
754
def _remote_lock_write(self, token):
756
branch_token = repo_token = ''
759
repo_token = self.repository.lock_write()
760
self.repository.unlock()
761
path = self.bzrdir._path_for_remote_call(self._client)
762
response = self._client.call('Branch.lock_write', path, branch_token,
764
if response[0] == 'ok':
765
ok, branch_token, repo_token = response
766
return branch_token, repo_token
767
elif response[0] == 'LockContention':
768
raise errors.LockContention('(remote lock)')
769
elif response[0] == 'TokenMismatch':
770
raise errors.TokenMismatch(token, '(remote token)')
771
elif response[0] == 'UnlockableTransport':
772
raise errors.UnlockableTransport(self.bzrdir.root_transport)
773
elif response[0] == 'ReadOnlyError':
774
raise errors.ReadOnlyError(self)
776
assert False, 'unexpected response code %r' % (response,)
778
def lock_write(self, token=None):
779
if not self._lock_mode:
780
remote_tokens = self._remote_lock_write(token)
781
self._lock_token, self._repo_lock_token = remote_tokens
782
assert self._lock_token, 'Remote server did not return a token!'
783
# TODO: We really, really, really don't want to call _ensure_real
784
# here, but it's the easiest way to ensure coherency between the
785
# state of the RemoteBranch and RemoteRepository objects and the
786
# physical locks. If we don't materialise the real objects here,
787
# then getting everything in the right state later is complex, so
788
# for now we just do it the lazy way.
789
# -- Andrew Bennetts, 2007-02-22.
791
if self._real_branch is not None:
792
self._real_branch.repository.lock_write(
793
token=self._repo_lock_token)
795
self._real_branch.lock_write(token=self._lock_token)
797
self._real_branch.repository.unlock()
798
if token is not None:
799
self._leave_lock = True
801
# XXX: this case seems to be unreachable; token cannot be None.
802
self._leave_lock = False
803
self._lock_mode = 'w'
805
elif self._lock_mode == 'r':
806
raise errors.ReadOnlyTransaction
808
if token is not None:
809
# A token was given to lock_write, and we're relocking, so check
810
# that the given token actually matches the one we already have.
811
if token != self._lock_token:
812
raise errors.TokenMismatch(token, self._lock_token)
813
self._lock_count += 1
814
return self._lock_token
816
def _unlock(self, branch_token, repo_token):
817
path = self.bzrdir._path_for_remote_call(self._client)
818
response = self._client.call('Branch.unlock', path, branch_token,
820
if response == ('ok',):
822
elif response[0] == 'TokenMismatch':
823
raise errors.TokenMismatch(
824
str((branch_token, repo_token)), '(remote tokens)')
826
assert False, 'unexpected response code %s' % (response,)
829
self._lock_count -= 1
830
if not self._lock_count:
831
self._clear_cached_state()
832
mode = self._lock_mode
833
self._lock_mode = None
834
if self._real_branch is not None:
835
if not self._leave_lock:
836
# If this RemoteBranch will remove the physical lock for the
837
# repository, make sure the _real_branch doesn't do it
838
# first. (Because the _real_branch's repository is set to
839
# be the RemoteRepository.)
840
self._real_branch.repository.leave_lock_in_place()
841
self._real_branch.unlock()
843
# Only write-locked branched need to make a remote method call
844
# to perfom the unlock.
846
assert self._lock_token, 'Locked, but no token!'
847
branch_token = self._lock_token
848
repo_token = self._repo_lock_token
849
self._lock_token = None
850
self._repo_lock_token = None
851
if not self._leave_lock:
852
self._unlock(branch_token, repo_token)
854
def break_lock(self):
856
return self._real_branch.break_lock()
858
def leave_lock_in_place(self):
859
self._leave_lock = True
861
def dont_leave_lock_in_place(self):
862
self._leave_lock = False
864
def last_revision_info(self):
865
"""See Branch.last_revision_info()."""
866
path = self.bzrdir._path_for_remote_call(self._client)
867
response = self._client.call('Branch.last_revision_info', path)
868
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
869
revno = int(response[1])
870
last_revision = response[2]
871
if last_revision == '':
872
last_revision = NULL_REVISION
873
return (revno, last_revision)
875
def _gen_revision_history(self):
876
"""See Branch._gen_revision_history()."""
877
path = self.bzrdir._path_for_remote_call(self._client)
878
response = self._client.call_expecting_body(
879
'Branch.revision_history', path)
880
assert response[0][0] == 'ok', ('unexpected response code %s'
882
result = response[1].read_body_bytes().split('\x00')
888
def set_revision_history(self, rev_history):
889
# Send just the tip revision of the history; the server will generate
890
# the full history from that. If the revision doesn't exist in this
891
# branch, NoSuchRevision will be raised.
892
path = self.bzrdir._path_for_remote_call(self._client)
893
if rev_history == []:
896
rev_id = rev_history[-1]
897
response = self._client.call('Branch.set_last_revision',
898
path, self._lock_token, self._repo_lock_token, rev_id)
899
if response[0] == 'NoSuchRevision':
900
raise NoSuchRevision(self, rev_id)
902
assert response == ('ok',), (
903
'unexpected response code %r' % (response,))
904
self._cache_revision_history(rev_history)
906
def get_parent(self):
908
return self._real_branch.get_parent()
910
def set_parent(self, url):
912
return self._real_branch.set_parent(url)
914
def get_config(self):
915
return RemoteBranchConfig(self)
917
def sprout(self, to_bzrdir, revision_id=None):
918
# Like Branch.sprout, except that it sprouts a branch in the default
919
# format, because RemoteBranches can't be created at arbitrary URLs.
920
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
921
# to_bzrdir.create_branch...
923
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
924
self._real_branch.copy_content_into(result, revision_id=revision_id)
925
result.set_parent(self.bzrdir.root_transport.base)
929
def append_revision(self, *revision_ids):
931
return self._real_branch.append_revision(*revision_ids)
934
def pull(self, source, overwrite=False, stop_revision=None):
936
self._real_branch.pull(
937
source, overwrite=overwrite, stop_revision=stop_revision)
940
def push(self, target, overwrite=False, stop_revision=None):
942
return self._real_branch.push(
943
target, overwrite=overwrite, stop_revision=stop_revision)
946
return self._lock_count >= 1
948
def set_last_revision_info(self, revno, revision_id):
950
self._clear_cached_state()
951
return self._real_branch.set_last_revision_info(revno, revision_id)
953
def generate_revision_history(self, revision_id, last_rev=None,
956
return self._real_branch.generate_revision_history(
957
revision_id, last_rev=last_rev, other_branch=other_branch)
962
return self._real_branch.tags
964
def set_push_location(self, location):
966
return self._real_branch.set_push_location(location)
968
def update_revisions(self, other, stop_revision=None):
970
return self._real_branch.update_revisions(
971
other, stop_revision=stop_revision)
974
class RemoteBranchConfig(BranchConfig):
977
self.branch._ensure_real()
978
return self.branch._real_branch.get_config().username()
980
def _get_branch_data_config(self):
981
self.branch._ensure_real()
982
if self._branch_data_config is None:
983
self._branch_data_config = TreeConfig(self.branch._real_branch)
984
return self._branch_data_config