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 by HPSS."""
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
# XXX: We should go into find_format, but not allow it to find
49
# RemoteBzrDirFormat and make sure it finds the real underlying format.
50
self._real_bzrdir = None
53
self._medium = transport.get_smart_client()
54
self._client = client.SmartClient(self._medium)
56
self._client = _client
61
path = self._path_for_remote_call(self._client)
62
#self._real_bzrdir._format.probe_transport(transport)
63
response = self._client.call('probe_dont_use', path)
64
if response == ('no',):
65
raise errors.NotBranchError(path=transport.base)
67
def _ensure_real(self):
68
"""Ensure that there is a _real_bzrdir set.
70
used before calls to self._real_bzrdir.
72
if not self._real_bzrdir:
73
default_format = BzrDirFormat.get_default_format()
74
self._real_bzrdir = default_format.open(self.root_transport,
77
def create_repository(self, shared=False):
78
self._real_bzrdir.create_repository(shared=shared)
79
return self.open_repository()
81
def create_branch(self):
82
real_branch = self._real_bzrdir.create_branch()
83
return RemoteBranch(self, self.find_repository(), real_branch)
85
def create_workingtree(self, revision_id=None):
86
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
87
return RemoteWorkingTree(self, real_workingtree)
89
def find_branch_format(self):
90
"""Find the branch 'format' for this bzrdir.
92
This might be a synthetic object for e.g. RemoteBranch and SVN.
94
b = self.open_branch()
97
def get_branch_reference(self):
98
"""See BzrDir.get_branch_reference()."""
99
path = self._path_for_remote_call(self._client)
100
response = self._client.call('BzrDir.open_branch', path)
101
if response[0] == 'ok':
102
if response[1] == '':
103
# branch at this location.
106
# a branch reference, use the existing BranchReference logic.
108
elif response == ('nobranch',):
109
raise errors.NotBranchError(path=self.root_transport.base)
111
assert False, 'unexpected response code %r' % (response,)
113
def open_branch(self, _unsupported=False):
114
assert _unsupported == False, 'unsupported flag support not implemented yet.'
115
reference_url = self.get_branch_reference()
116
if reference_url is None:
117
# branch at this location.
118
return RemoteBranch(self, self.find_repository())
120
# a branch reference, use the existing BranchReference logic.
121
format = BranchReferenceFormat()
122
return format.open(self, _found=True, location=reference_url)
124
def open_repository(self):
125
path = self._path_for_remote_call(self._client)
126
response = self._client.call('BzrDir.find_repository', path)
127
assert response[0] in ('ok', 'norepository'), \
128
'unexpected response code %s' % (response,)
129
if response[0] == 'norepository':
130
raise errors.NoRepositoryPresent(self)
131
assert len(response) == 4, 'incorrect response length %s' % (response,)
132
if response[1] == '':
133
format = RemoteRepositoryFormat()
134
format.rich_root_data = response[2] == 'True'
135
format.supports_tree_reference = response[3] == 'True'
136
return RemoteRepository(self, format)
138
raise errors.NoRepositoryPresent(self)
140
def open_workingtree(self, recommend_upgrade=True):
141
raise errors.NotLocalUrl(self.root_transport)
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):
148
return self._real_bzrdir.get_branch_transport(branch_format)
150
def get_repository_transport(self, repository_format):
151
return self._real_bzrdir.get_repository_transport(repository_format)
153
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 everything is delegated to IO-like operations over
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
assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
265
if response[0][0] == 'ok':
266
coded = response[1].read_body_bytes()
268
# no revisions in this repository!
270
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 ('ok', 'no'), 'unexpected response code %s' % (response,)
291
return response[0] == 'ok'
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()
417
assert self._lock_token, 'Locked, but no token!'
418
token = self._lock_token
419
self._lock_token = None
420
if not self._leave_lock:
423
def break_lock(self):
424
# should hand off to the network
426
return self._real_repository.break_lock()
428
### These methods are just thin shims to the VFS object for now.
430
def revision_tree(self, revision_id):
432
return self._real_repository.revision_tree(revision_id)
434
def get_commit_builder(self, branch, parents, config, timestamp=None,
435
timezone=None, committer=None, revprops=None,
437
# FIXME: It ought to be possible to call this without immediately
438
# triggering _ensure_real. For now it's the easiest thing to do.
440
builder = self._real_repository.get_commit_builder(branch, parents,
441
config, timestamp=timestamp, timezone=timezone,
442
committer=committer, revprops=revprops, revision_id=revision_id)
443
# Make the builder use this RemoteRepository rather than the real one.
444
builder.repository = self
448
def add_inventory(self, revid, inv, parents):
450
return self._real_repository.add_inventory(revid, inv, parents)
453
def add_revision(self, rev_id, rev, inv=None, config=None):
455
return self._real_repository.add_revision(
456
rev_id, rev, inv=inv, config=config)
459
def get_inventory(self, revision_id):
461
return self._real_repository.get_inventory(revision_id)
464
def get_revision(self, revision_id):
466
return self._real_repository.get_revision(revision_id)
469
def weave_store(self):
471
return self._real_repository.weave_store
473
def get_transaction(self):
475
return self._real_repository.get_transaction()
478
def clone(self, a_bzrdir, revision_id=None):
480
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
482
def make_working_trees(self):
483
"""RemoteRepositories never create working trees by default."""
486
def fetch(self, source, revision_id=None, pb=None):
488
return self._real_repository.fetch(
489
source, revision_id=revision_id, pb=pb)
492
def control_weaves(self):
494
return self._real_repository.control_weaves
497
def get_ancestry(self, revision_id):
499
return self._real_repository.get_ancestry(revision_id)
502
def get_inventory_weave(self):
504
return self._real_repository.get_inventory_weave()
506
def fileids_altered_by_revision_ids(self, revision_ids):
508
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
511
def get_signature_text(self, revision_id):
513
return self._real_repository.get_signature_text(revision_id)
516
def get_revision_graph_with_ghosts(self, revision_ids=None):
518
return self._real_repository.get_revision_graph_with_ghosts(
519
revision_ids=revision_ids)
522
def get_inventory_xml(self, revision_id):
524
return self._real_repository.get_inventory_xml(revision_id)
526
def deserialise_inventory(self, revision_id, xml):
528
return self._real_repository.deserialise_inventory(revision_id, xml)
530
def reconcile(self, other=None, thorough=False):
532
return self._real_repository.reconcile(other=other, thorough=thorough)
534
def all_revision_ids(self):
536
return self._real_repository.all_revision_ids()
539
def get_deltas_for_revisions(self, revisions):
541
return self._real_repository.get_deltas_for_revisions(revisions)
544
def get_revision_delta(self, revision_id):
546
return self._real_repository.get_revision_delta(revision_id)
549
def revision_trees(self, revision_ids):
551
return self._real_repository.revision_trees(revision_ids)
554
def get_revision_reconcile(self, revision_id):
556
return self._real_repository.get_revision_reconcile(revision_id)
559
def check(self, revision_ids):
561
return self._real_repository.check(revision_ids)
563
def copy_content_into(self, destination, revision_id=None):
565
return self._real_repository.copy_content_into(
566
destination, revision_id=revision_id)
568
def set_make_working_trees(self, new_value):
569
raise NotImplementedError(self.set_make_working_trees)
572
def sign_revision(self, revision_id, gpg_strategy):
574
return self._real_repository.sign_revision(revision_id, gpg_strategy)
577
def get_revisions(self, revision_ids):
579
return self._real_repository.get_revisions(revision_ids)
581
def supports_rich_root(self):
583
return self._real_repository.supports_rich_root()
585
def iter_reverse_revision_history(self, revision_id):
587
return self._real_repository.iter_reverse_revision_history(revision_id)
590
def _serializer(self):
592
return self._real_repository._serializer
594
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
596
return self._real_repository.store_revision_signature(
597
gpg_strategy, plaintext, revision_id)
599
def has_signature_for_revision_id(self, revision_id):
601
return self._real_repository.has_signature_for_revision_id(revision_id)
604
class RemoteBranchLockableFiles(LockableFiles):
605
"""A 'LockableFiles' implementation that talks to a smart server.
607
This is not a public interface class.
610
def __init__(self, bzrdir, _client):
612
self._client = _client
613
self._need_find_modes = True
614
# XXX: This assumes that the branch control directory is .bzr/branch,
615
# which isn't necessarily true.
616
LockableFiles.__init__(
617
self, bzrdir.root_transport.clone('.bzr/branch'),
618
'lock', lockdir.LockDir)
620
def _find_modes(self):
621
# RemoteBranches don't let the client set the mode of control files.
622
self._dir_mode = None
623
self._file_mode = None
626
"""'get' a remote path as per the LockableFiles interface.
628
:param path: the file to 'get'. If this is 'branch.conf', we do not
629
just retrieve a file, instead we ask the smart server to generate
630
a configuration for us - which is retrieved as an INI file.
632
if path == 'branch.conf':
633
path = self.bzrdir._path_for_remote_call(self._client)
634
response = self._client.call_expecting_body(
635
'Branch.get_config_file', path)
636
assert response[0][0] == 'ok', \
637
'unexpected response code %s' % (response[0],)
638
return StringIO(response[1].read_body_bytes())
641
return LockableFiles.get(self, path)
644
class RemoteBranchFormat(branch.BranchFormat):
646
def __eq__(self, other):
647
return (isinstance(other, RemoteBranchFormat) and
648
self.__dict__ == other.__dict__)
650
def get_format_description(self):
651
return 'Remote BZR Branch'
653
def get_format_string(self):
654
return 'Remote BZR Branch'
656
def open(self, a_bzrdir):
657
assert isinstance(a_bzrdir, RemoteBzrDir)
658
return a_bzrdir.open_branch()
660
def initialize(self, a_bzrdir):
661
assert isinstance(a_bzrdir, RemoteBzrDir)
662
return a_bzrdir.create_branch()
665
class RemoteBranch(branch.Branch):
666
"""Branch stored on a server accessed by HPSS RPC.
668
At the moment most operations are mapped down to simple file operations.
671
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
673
"""Create a RemoteBranch instance.
675
:param real_branch: An optional local implementation of the branch
676
format, usually accessing the data via the VFS.
677
:param _client: Private parameter for testing.
679
#branch.Branch.__init__(self)
680
self._revision_history_cache = None
681
self.bzrdir = remote_bzrdir
682
if _client is not None:
683
self._client = _client
685
self._client = client.SmartClient(self.bzrdir._medium)
686
self.repository = remote_repository
687
if real_branch is not None:
688
self._real_branch = real_branch
689
# Give the remote repository the matching real repo.
690
real_repo = self._real_branch.repository
691
if isinstance(real_repo, RemoteRepository):
692
real_repo._ensure_real()
693
real_repo = real_repo._real_repository
694
self.repository._set_real_repository(real_repo)
695
# Give the branch the remote repository to let fast-pathing happen.
696
self._real_branch.repository = self.repository
698
self._real_branch = None
699
# Fill out expected attributes of branch for bzrlib api users.
700
self._format = RemoteBranchFormat()
701
self.base = self.bzrdir.root_transport.base
702
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
703
self._lock_mode = None
704
self._lock_token = None
706
self._leave_lock = False
708
def _ensure_real(self):
709
"""Ensure that there is a _real_branch set.
711
used before calls to self._real_branch.
713
if not self._real_branch:
714
assert vfs.vfs_enabled()
715
self.bzrdir._ensure_real()
716
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
717
# Give the remote repository the matching real repo.
718
real_repo = self._real_branch.repository
719
if isinstance(real_repo, RemoteRepository):
720
real_repo._ensure_real()
721
real_repo = real_repo._real_repository
722
self.repository._set_real_repository(real_repo)
723
# Give the branch the remote repository to let fast-pathing happen.
724
self._real_branch.repository = self.repository
725
# XXX: deal with _lock_mode == 'w'
726
if self._lock_mode == 'r':
727
self._real_branch.lock_read()
729
def get_physical_lock_status(self):
730
"""See Branch.get_physical_lock_status()."""
731
# should be an API call to the server, as branches must be lockable.
733
return self._real_branch.get_physical_lock_status()
736
if not self._lock_mode:
737
self._lock_mode = 'r'
739
if self._real_branch is not None:
740
self._real_branch.lock_read()
742
self._lock_count += 1
744
def _remote_lock_write(self, token):
746
branch_token = repo_token = ''
749
repo_token = self.repository.lock_write()
750
self.repository.unlock()
751
path = self.bzrdir._path_for_remote_call(self._client)
752
response = self._client.call('Branch.lock_write', path, branch_token,
754
if response[0] == 'ok':
755
ok, branch_token, repo_token = response
756
return branch_token, repo_token
757
elif response[0] == 'LockContention':
758
raise errors.LockContention('(remote lock)')
759
elif response[0] == 'TokenMismatch':
760
raise errors.TokenMismatch(token, '(remote token)')
761
elif response[0] == 'UnlockableTransport':
762
raise errors.UnlockableTransport(self.bzrdir.root_transport)
763
elif response[0] == 'ReadOnlyError':
764
raise errors.ReadOnlyError(self)
766
assert False, 'unexpected response code %r' % (response,)
768
def lock_write(self, token=None):
769
if not self._lock_mode:
770
remote_tokens = self._remote_lock_write(token)
771
self._lock_token, self._repo_lock_token = remote_tokens
772
assert self._lock_token, 'Remote server did not return a token!'
773
# TODO: We really, really, really don't want to call _ensure_real
774
# here, but it's the easiest way to ensure coherency between the
775
# state of the RemoteBranch and RemoteRepository objects and the
776
# physical locks. If we don't materialise the real objects here,
777
# then getting everything in the right state later is complex, so
778
# for now we just do it the lazy way.
779
# -- Andrew Bennetts, 2007-02-22.
781
if self._real_branch is not None:
782
self._real_branch.repository.lock_write(
783
token=self._repo_lock_token)
785
self._real_branch.lock_write(token=self._lock_token)
787
self._real_branch.repository.unlock()
788
if token is not None:
789
self._leave_lock = True
791
# XXX: this case seems to be unreachable; token cannot be None.
792
self._leave_lock = False
793
self._lock_mode = 'w'
795
elif self._lock_mode == 'r':
796
raise errors.ReadOnlyTransaction
798
if token is not None:
799
# A token was given to lock_write, and we're relocking, so check
800
# that the given token actually matches the one we already have.
801
if token != self._lock_token:
802
raise errors.TokenMismatch(token, self._lock_token)
803
self._lock_count += 1
804
return self._lock_token
806
def _unlock(self, branch_token, repo_token):
807
path = self.bzrdir._path_for_remote_call(self._client)
808
response = self._client.call('Branch.unlock', path, branch_token,
810
if response == ('ok',):
812
elif response[0] == 'TokenMismatch':
813
raise errors.TokenMismatch(
814
str((branch_token, repo_token)), '(remote tokens)')
816
assert False, 'unexpected response code %s' % (response,)
819
self._lock_count -= 1
820
if not self._lock_count:
821
self._clear_cached_state()
822
mode = self._lock_mode
823
self._lock_mode = None
824
if self._real_branch is not None:
825
if not self._leave_lock:
826
# If this RemoteBranch will remove the physical lock for the
827
# repository, make sure the _real_branch doesn't do it
828
# first. (Because the _real_branch's repository is set to
829
# be the RemoteRepository.)
830
self._real_branch.repository.leave_lock_in_place()
831
self._real_branch.unlock()
834
assert self._lock_token, 'Locked, but no token!'
835
branch_token = self._lock_token
836
repo_token = self._repo_lock_token
837
self._lock_token = None
838
self._repo_lock_token = None
839
if not self._leave_lock:
840
self._unlock(branch_token, repo_token)
842
def break_lock(self):
844
return self._real_branch.break_lock()
846
def leave_lock_in_place(self):
847
self._leave_lock = True
849
def dont_leave_lock_in_place(self):
850
self._leave_lock = False
852
def last_revision_info(self):
853
"""See Branch.last_revision_info()."""
854
path = self.bzrdir._path_for_remote_call(self._client)
855
response = self._client.call('Branch.last_revision_info', path)
856
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
857
revno = int(response[1])
858
last_revision = response[2]
859
if last_revision == '':
860
last_revision = NULL_REVISION
861
return (revno, last_revision)
863
def _gen_revision_history(self):
864
"""See Branch._gen_revision_history()."""
865
path = self.bzrdir._path_for_remote_call(self._client)
866
response = self._client.call_expecting_body(
867
'Branch.revision_history', path)
868
assert response[0][0] == 'ok', ('unexpected response code %s'
870
result = response[1].read_body_bytes().split('\x00')
876
def set_revision_history(self, rev_history):
877
# Send just the tip revision of the history; the server will generate
878
# the full history from that. If the revision doesn't exist in this
879
# branch, NoSuchRevision will be raised.
880
path = self.bzrdir._path_for_remote_call(self._client)
881
if rev_history == []:
884
rev_id = rev_history[-1]
885
response = self._client.call('Branch.set_last_revision',
886
path, self._lock_token, self._repo_lock_token, rev_id)
887
if response[0] == 'NoSuchRevision':
888
raise NoSuchRevision(self, rev_id)
890
assert response == ('ok',), (
891
'unexpected response code %r' % (response,))
892
self._cache_revision_history(rev_history)
894
def get_parent(self):
896
return self._real_branch.get_parent()
898
def set_parent(self, url):
900
return self._real_branch.set_parent(url)
902
def get_config(self):
903
return RemoteBranchConfig(self)
905
def sprout(self, to_bzrdir, revision_id=None):
906
# Like Branch.sprout, except that it sprouts a branch in the default
907
# format, because RemoteBranches can't be created at arbitrary URLs.
908
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
909
# to_bzrdir.create_branch...
911
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
912
self._real_branch.copy_content_into(result, revision_id=revision_id)
913
result.set_parent(self.bzrdir.root_transport.base)
917
def append_revision(self, *revision_ids):
919
return self._real_branch.append_revision(*revision_ids)
922
def pull(self, source, overwrite=False, stop_revision=None):
924
self._real_branch.pull(
925
source, overwrite=overwrite, stop_revision=stop_revision)
928
def push(self, target, overwrite=False, stop_revision=None):
930
return self._real_branch.push(
931
target, overwrite=overwrite, stop_revision=stop_revision)
934
return self._lock_count >= 1
936
def set_last_revision_info(self, revno, revision_id):
938
self._clear_cached_state()
939
return self._real_branch.set_last_revision_info(revno, revision_id)
941
def generate_revision_history(self, revision_id, last_rev=None,
944
return self._real_branch.generate_revision_history(
945
revision_id, last_rev=last_rev, other_branch=other_branch)
950
return self._real_branch.tags
952
def set_push_location(self, location):
954
return self._real_branch.set_push_location(location)
956
def update_revisions(self, other, stop_revision=None):
958
return self._real_branch.update_revisions(
959
other, stop_revision=stop_revision)
962
class RemoteBranchConfig(BranchConfig):
965
self.branch._ensure_real()
966
return self.branch._real_branch.get_config().username()
968
def _get_branch_data_config(self):
969
self.branch._ensure_real()
970
if self._branch_data_config is None:
971
self._branch_data_config = TreeConfig(self.branch._real_branch)
972
return self._branch_data_config