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.support_tree_reference = response[3] == 'True'
136
return RemoteRepository(self, format)
138
raise errors.NoRepositoryPresent(self)
140
def open_workingtree(self):
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, basis=None, force_new_repo=False):
166
return self._real_bzrdir.clone(url, revision_id=revision_id,
167
basis=basis, force_new_repo=force_new_repo)
169
#def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
170
# self._ensure_real()
171
# return self._real_bzrdir.sprout(url, revision_id=revision_id,
172
# basis=basis, force_new_repo=force_new_repo)
175
class RemoteRepositoryFormat(repository.RepositoryFormat):
176
"""Format for repositories accessed over a SmartClient.
178
Instances of this repository are represented by RemoteRepository
181
The RemoteRepositoryFormat is parameterised during construction
182
to reflect the capabilities of the real, remote format. Specifically
183
the attributes rich_root_data and support_tree_reference are set
184
on a per instance basis, and are not set (and should not be) at
188
_matchingbzrdir = RemoteBzrDirFormat
190
def initialize(self, a_bzrdir, shared=False):
191
assert isinstance(a_bzrdir, RemoteBzrDir)
192
return a_bzrdir.create_repository(shared=shared)
194
def open(self, a_bzrdir):
195
assert isinstance(a_bzrdir, RemoteBzrDir)
196
return a_bzrdir.open_repository()
198
def get_format_description(self):
199
return 'bzr remote repository'
201
def __eq__(self, other):
202
return self.__class__ == other.__class__
204
def check_conversion_target(self, target_format):
205
if self.rich_root_data and not target_format.rich_root_data:
206
raise errors.BadConversionTarget(
207
'Does not support rich root data.', target_format)
208
if (self.support_tree_reference and
209
not getattr(target_format, 'support_tree_reference', False)):
210
raise errors.BadConversionTarget(
211
'Does not support nested trees', target_format)
214
class RemoteRepository(object):
215
"""Repository accessed over rpc.
217
For the moment everything is delegated to IO-like operations over
221
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
222
"""Create a RemoteRepository instance.
224
:param remote_bzrdir: The bzrdir hosting this repository.
225
:param format: The RemoteFormat object to use.
226
:param real_repository: If not None, a local implementation of the
227
repository logic for the repository, usually accessing the data
229
:param _client: Private testing parameter - override the smart client
230
to be used by the repository.
233
self._real_repository = real_repository
235
self._real_repository = None
236
self.bzrdir = remote_bzrdir
238
self._client = client.SmartClient(self.bzrdir._medium)
240
self._client = _client
241
self._format = format
242
self._lock_mode = None
243
self._lock_token = None
245
self._leave_lock = False
247
def _ensure_real(self):
248
"""Ensure that there is a _real_repository set.
250
used before calls to self._real_repository.
252
if not self._real_repository:
253
self.bzrdir._ensure_real()
254
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
255
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
257
def get_revision_graph(self, revision_id=None):
258
"""See Repository.get_revision_graph()."""
259
if revision_id is None:
261
elif revision_id == NULL_REVISION:
264
path = self.bzrdir._path_for_remote_call(self._client)
265
assert type(revision_id) is str
266
response = self._client.call2(
267
'Repository.get_revision_graph', path, revision_id)
268
assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
269
if response[0][0] == 'ok':
270
coded = response[1].read_body_bytes()
272
# no revisions in this repository!
274
lines = coded.split('\n')
278
d = list(line.split())
279
revision_graph[d[0]] = d[1:]
281
return revision_graph
283
response_body = response[1].read_body_bytes()
284
assert response_body == ''
285
raise NoSuchRevision(self, revision_id)
287
def has_revision(self, revision_id):
288
"""See Repository.has_revision()."""
289
if revision_id is None:
290
# The null revision is always present.
292
path = self.bzrdir._path_for_remote_call(self._client)
293
response = self._client.call('Repository.has_revision', path, revision_id)
294
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
295
return response[0] == 'ok'
297
def gather_stats(self, revid=None, committers=None):
298
"""See Repository.gather_stats()."""
299
path = self.bzrdir._path_for_remote_call(self._client)
300
if revid in (None, NULL_REVISION):
304
if committers is None or not committers:
305
fmt_committers = 'no'
307
fmt_committers = 'yes'
308
response = self._client.call2('Repository.gather_stats', path,
309
fmt_revid, fmt_committers)
310
assert response[0][0] == 'ok', \
311
'unexpected response code %s' % (response[0],)
313
body = response[1].read_body_bytes()
315
for line in body.split('\n'):
318
key, val_text = line.split(':')
319
if key in ('revisions', 'size', 'committers'):
320
result[key] = int(val_text)
321
elif key in ('firstrev', 'latestrev'):
322
values = val_text.split(' ')[1:]
323
result[key] = (float(values[0]), long(values[1]))
327
def get_physical_lock_status(self):
328
"""See Repository.get_physical_lock_status()."""
332
"""See Repository.is_shared()."""
333
path = self.bzrdir._path_for_remote_call(self._client)
334
response = self._client.call('Repository.is_shared', path)
335
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
336
return response[0] == 'yes'
339
# wrong eventually - want a local lock cache context
340
if not self._lock_mode:
341
self._lock_mode = 'r'
343
if self._real_repository is not None:
344
self._real_repository.lock_read()
346
self._lock_count += 1
348
def _remote_lock_write(self, token):
349
path = self.bzrdir._path_for_remote_call(self._client)
352
response = self._client.call('Repository.lock_write', path, token)
353
if response[0] == 'ok':
356
elif response[0] == 'LockContention':
357
raise errors.LockContention('(remote lock)')
358
elif response[0] == 'UnlockableTransport':
359
raise errors.UnlockableTransport(self.bzrdir.root_transport)
361
assert False, 'unexpected response code %s' % (response,)
363
def lock_write(self, token=None):
364
if not self._lock_mode:
365
self._lock_token = self._remote_lock_write(token)
366
assert self._lock_token, 'Remote server did not return a token!'
367
if self._real_repository is not None:
368
self._real_repository.lock_write(token=self._lock_token)
369
if token is not None:
370
self._leave_lock = True
372
self._leave_lock = False
373
self._lock_mode = 'w'
375
elif self._lock_mode == 'r':
376
raise errors.ReadOnlyError(self)
378
self._lock_count += 1
379
return self._lock_token
381
def leave_lock_in_place(self):
382
self._leave_lock = True
384
def dont_leave_lock_in_place(self):
385
self._leave_lock = False
387
def _set_real_repository(self, repository):
388
"""Set the _real_repository for this repository.
390
:param repository: The repository to fallback to for non-hpss
391
implemented operations.
393
assert not isinstance(repository, RemoteRepository)
394
self._real_repository = repository
395
if self._lock_mode == 'w':
396
# if we are already locked, the real repository must be able to
397
# acquire the lock with our token.
398
self._real_repository.lock_write(self._lock_token)
399
elif self._lock_mode == 'r':
400
self._real_repository.lock_read()
402
def _unlock(self, token):
403
path = self.bzrdir._path_for_remote_call(self._client)
404
response = self._client.call('Repository.unlock', path, token)
405
if response == ('ok',):
407
elif response[0] == 'TokenMismatch':
408
raise errors.TokenMismatch(token, '(remote token)')
410
assert False, 'unexpected response code %s' % (response,)
413
self._lock_count -= 1
414
if not self._lock_count:
415
mode = self._lock_mode
416
self._lock_mode = None
417
if self._real_repository is not None:
418
self._real_repository.unlock()
421
assert self._lock_token, 'Locked, but no token!'
422
token = self._lock_token
423
self._lock_token = None
424
if not self._leave_lock:
427
def break_lock(self):
428
# should hand off to the network
430
return self._real_repository.break_lock()
432
### These methods are just thin shims to the VFS object for now.
434
def revision_tree(self, revision_id):
436
return self._real_repository.revision_tree(revision_id)
438
def get_commit_builder(self, branch, parents, config, timestamp=None,
439
timezone=None, committer=None, revprops=None,
441
# FIXME: It ought to be possible to call this without immediately
442
# triggering _ensure_real. For now it's the easiest thing to do.
444
builder = self._real_repository.get_commit_builder(branch, parents,
445
config, timestamp=timestamp, timezone=timezone,
446
committer=committer, revprops=revprops, revision_id=revision_id)
447
# Make the builder use this RemoteRepository rather than the real one.
448
builder.repository = self
452
def add_inventory(self, revid, inv, parents):
454
return self._real_repository.add_inventory(revid, inv, parents)
457
def add_revision(self, rev_id, rev, inv=None, config=None):
459
return self._real_repository.add_revision(
460
rev_id, rev, inv=inv, config=config)
463
def get_inventory(self, revision_id):
465
return self._real_repository.get_inventory(revision_id)
468
def get_revision(self, revision_id):
470
return self._real_repository.get_revision(revision_id)
473
def weave_store(self):
475
return self._real_repository.weave_store
477
def get_transaction(self):
479
return self._real_repository.get_transaction()
482
def clone(self, a_bzrdir, revision_id=None, basis=None):
484
return self._real_repository.clone(
485
a_bzrdir, revision_id=revision_id, basis=basis)
487
def make_working_trees(self):
488
"""RemoteRepositories never create working trees by default."""
491
def fetch(self, source, revision_id=None, pb=None):
493
return self._real_repository.fetch(
494
source, revision_id=revision_id, pb=pb)
497
def control_weaves(self):
499
return self._real_repository.control_weaves
502
def get_ancestry(self, revision_id):
504
return self._real_repository.get_ancestry(revision_id)
507
def get_inventory_weave(self):
509
return self._real_repository.get_inventory_weave()
511
def fileids_altered_by_revision_ids(self, revision_ids):
513
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
516
def get_signature_text(self, revision_id):
518
return self._real_repository.get_signature_text(revision_id)
521
def get_revision_graph_with_ghosts(self, revision_ids=None):
523
return self._real_repository.get_revision_graph_with_ghosts(
524
revision_ids=revision_ids)
527
def get_inventory_xml(self, revision_id):
529
return self._real_repository.get_inventory_xml(revision_id)
531
def deserialise_inventory(self, revision_id, xml):
533
return self._real_repository.deserialise_inventory(revision_id, xml)
535
def reconcile(self, other=None, thorough=False):
537
return self._real_repository.reconcile(other=other, thorough=thorough)
539
def all_revision_ids(self):
541
return self._real_repository.all_revision_ids()
544
def get_deltas_for_revisions(self, revisions):
546
return self._real_repository.get_deltas_for_revisions(revisions)
549
def get_revision_delta(self, revision_id):
551
return self._real_repository.get_revision_delta(revision_id)
554
def revision_trees(self, revision_ids):
556
return self._real_repository.revision_trees(revision_ids)
559
def get_revision_reconcile(self, revision_id):
561
return self._real_repository.get_revision_reconcile(revision_id)
564
def check(self, revision_ids):
566
return self._real_repository.check(revision_ids)
568
def copy_content_into(self, destination, revision_id=None, basis=None):
570
return self._real_repository.copy_content_into(
571
destination, revision_id=revision_id, basis=basis)
573
def set_make_working_trees(self, new_value):
574
raise NotImplementedError(self.set_make_working_trees)
577
def sign_revision(self, revision_id, gpg_strategy):
579
return self._real_repository.sign_revision(revision_id, gpg_strategy)
582
def get_revisions(self, revision_ids):
584
return self._real_repository.get_revisions(revision_ids)
586
def supports_rich_root(self):
588
return self._real_repository.supports_rich_root()
590
def iter_reverse_revision_history(self, revision_id):
592
return self._real_repository.iter_reverse_revision_history(revision_id)
595
def _serializer(self):
597
return self._real_repository._serializer
599
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
601
return self._real_repository.store_revision_signature(
602
gpg_strategy, plaintext, revision_id)
604
def has_signature_for_revision_id(self, revision_id):
606
return self._real_repository.has_signature_for_revision_id(revision_id)
609
class RemoteBranchLockableFiles(LockableFiles):
610
"""A 'LockableFiles' implementation that talks to a smart server.
612
This is not a public interface class.
615
def __init__(self, bzrdir, _client):
617
self._client = _client
618
# XXX: This assumes that the branch control directory is .bzr/branch,
619
# which isn't necessarily true.
620
LockableFiles.__init__(
621
self, bzrdir.root_transport.clone('.bzr/branch'),
622
'lock', lockdir.LockDir)
625
"""'get' a remote path as per the LockableFiles interface.
627
:param path: the file to 'get'. If this is 'branch.conf', we do not
628
just retrieve a file, instead we ask the smart server to generate
629
a configuration for us - which is retrieved as an INI file.
631
if path == 'branch.conf':
632
path = self.bzrdir._path_for_remote_call(self._client)
633
response = self._client.call2('Branch.get_config_file', path)
634
assert response[0][0] == 'ok', \
635
'unexpected response code %s' % (response[0],)
636
return StringIO(response[1].read_body_bytes())
639
return LockableFiles.get(self, path)
642
class RemoteBranchFormat(branch.BranchFormat):
644
def __eq__(self, other):
645
return (isinstance(other, RemoteBranchFormat) and
646
self.__dict__ == other.__dict__)
648
def get_format_description(self):
649
return 'Remote BZR Branch'
651
def get_format_string(self):
652
return 'Remote BZR Branch'
654
def open(self, a_bzrdir):
655
assert isinstance(a_bzrdir, RemoteBzrDir)
656
return a_bzrdir.open_branch()
658
def initialize(self, a_bzrdir):
659
assert isinstance(a_bzrdir, RemoteBzrDir)
660
return a_bzrdir.create_branch()
663
class RemoteBranch(branch.Branch):
664
"""Branch stored on a server accessed by HPSS RPC.
666
At the moment most operations are mapped down to simple file operations.
669
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
671
"""Create a RemoteBranch instance.
673
:param real_branch: An optional local implementation of the branch
674
format, usually accessing the data via the VFS.
675
:param _client: Private parameter for testing.
677
#branch.Branch.__init__(self)
678
self._revision_history_cache = None
679
self.bzrdir = remote_bzrdir
680
if _client is not None:
681
self._client = _client
683
self._client = client.SmartClient(self.bzrdir._medium)
684
self.repository = remote_repository
685
if real_branch is not None:
686
self._real_branch = real_branch
687
# Give the remote repository the matching real repo.
688
real_repo = self._real_branch.repository
689
if isinstance(real_repo, RemoteRepository):
690
real_repo._ensure_real()
691
real_repo = real_repo._real_repository
692
self.repository._set_real_repository(real_repo)
693
# Give the branch the remote repository to let fast-pathing happen.
694
self._real_branch.repository = self.repository
696
self._real_branch = None
697
# Fill out expected attributes of branch for bzrlib api users.
698
self._format = RemoteBranchFormat()
699
self.base = self.bzrdir.root_transport.base
700
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
701
self._lock_mode = None
702
self._lock_token = None
704
self._leave_lock = False
706
def _ensure_real(self):
707
"""Ensure that there is a _real_branch set.
709
used before calls to self._real_branch.
711
if not self._real_branch:
712
assert vfs.vfs_enabled()
713
self.bzrdir._ensure_real()
714
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
715
# Give the remote repository the matching real repo.
716
real_repo = self._real_branch.repository
717
if isinstance(real_repo, RemoteRepository):
718
real_repo._ensure_real()
719
real_repo = real_repo._real_repository
720
self.repository._set_real_repository(real_repo)
721
# Give the branch the remote repository to let fast-pathing happen.
722
self._real_branch.repository = self.repository
723
# XXX: deal with _lock_mode == 'w'
724
if self._lock_mode == 'r':
725
self._real_branch.lock_read()
727
def get_physical_lock_status(self):
728
"""See Branch.get_physical_lock_status()."""
729
# should be an API call to the server, as branches must be lockable.
731
return self._real_branch.get_physical_lock_status()
734
if not self._lock_mode:
735
self._lock_mode = 'r'
737
if self._real_branch is not None:
738
self._real_branch.lock_read()
740
self._lock_count += 1
742
def _remote_lock_write(self, tokens):
744
branch_token = repo_token = ''
746
branch_token, repo_token = tokens
747
path = self.bzrdir._path_for_remote_call(self._client)
748
response = self._client.call('Branch.lock_write', path, branch_token,
750
if response[0] == 'ok':
751
ok, branch_token, repo_token = response
752
return branch_token, repo_token
753
elif response[0] == 'LockContention':
754
raise errors.LockContention('(remote lock)')
755
elif response[0] == 'TokenMismatch':
756
raise errors.TokenMismatch(tokens, '(remote tokens)')
757
elif response[0] == 'UnlockableTransport':
758
raise errors.UnlockableTransport(self.bzrdir.root_transport)
759
elif response[0] == 'ReadOnlyError':
760
raise errors.ReadOnlyError(self)
762
assert False, 'unexpected response code %r' % (response,)
764
def lock_write(self, tokens=None):
765
if not self._lock_mode:
766
remote_tokens = self._remote_lock_write(tokens)
767
self._lock_token, self._repo_lock_token = remote_tokens
768
assert self._lock_token, 'Remote server did not return a token!'
769
# TODO: We really, really, really don't want to call _ensure_real
770
# here, but it's the easiest way to ensure coherency between the
771
# state of the RemoteBranch and RemoteRepository objects and the
772
# physical locks. If we don't materialise the real objects here,
773
# then getting everything in the right state later is complex, so
774
# for now we just do it the lazy way.
775
# -- Andrew Bennetts, 2007-02-22.
777
if self._real_branch is not None:
778
self._real_branch.lock_write(tokens=remote_tokens)
779
if tokens is not None:
780
self._leave_lock = True
782
# XXX: this case seems to be unreachable; tokens cannot be None.
783
self._leave_lock = False
784
self._lock_mode = 'w'
786
elif self._lock_mode == 'r':
787
raise errors.ReadOnlyTransaction
789
if tokens is not None:
790
# Tokens were given to lock_write, and we're relocking, so check
791
# that the given tokens actually match the ones we already have.
792
held_tokens = (self._lock_token, self._repo_lock_token)
793
if tokens != held_tokens:
794
raise errors.TokenMismatch(str(tokens), str(held_tokens))
795
self._lock_count += 1
796
return self._lock_token, self._repo_lock_token
798
def _unlock(self, branch_token, repo_token):
799
path = self.bzrdir._path_for_remote_call(self._client)
800
response = self._client.call('Branch.unlock', path, branch_token,
802
if response == ('ok',):
804
elif response[0] == 'TokenMismatch':
805
raise errors.TokenMismatch(
806
str((branch_token, repo_token)), '(remote tokens)')
808
assert False, 'unexpected response code %s' % (response,)
811
self._lock_count -= 1
812
if not self._lock_count:
813
self._clear_cached_state()
814
mode = self._lock_mode
815
self._lock_mode = None
816
if self._real_branch is not None:
817
if not self._leave_lock:
818
# If this RemoteBranch will remove the physical lock for the
819
# repository, make sure the _real_branch doesn't do it
820
# first. (Because the _real_branch's repository is set to
821
# be the RemoteRepository.)
822
self._real_branch.repository.leave_lock_in_place()
823
self._real_branch.unlock()
826
assert self._lock_token, 'Locked, but no token!'
827
branch_token = self._lock_token
828
repo_token = self._repo_lock_token
829
self._lock_token = None
830
self._repo_lock_token = None
831
if not self._leave_lock:
832
self._unlock(branch_token, repo_token)
834
def break_lock(self):
836
return self._real_branch.break_lock()
838
def leave_lock_in_place(self):
839
self._leave_lock = True
841
def dont_leave_lock_in_place(self):
842
self._leave_lock = False
844
def last_revision_info(self):
845
"""See Branch.last_revision_info()."""
846
path = self.bzrdir._path_for_remote_call(self._client)
847
response = self._client.call('Branch.last_revision_info', path)
848
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
849
revno = int(response[1])
850
last_revision = response[2]
851
if last_revision == '':
852
last_revision = NULL_REVISION
853
return (revno, last_revision)
855
def _gen_revision_history(self):
856
"""See Branch._gen_revision_history()."""
857
path = self.bzrdir._path_for_remote_call(self._client)
858
response = self._client.call2('Branch.revision_history', path)
859
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
860
result = response[1].read_body_bytes().split('\x00')
866
def set_revision_history(self, rev_history):
867
# Send just the tip revision of the history; the server will generate
868
# the full history from that. If the revision doesn't exist in this
869
# branch, NoSuchRevision will be raised.
870
path = self.bzrdir._path_for_remote_call(self._client)
871
if rev_history == []:
874
rev_id = rev_history[-1]
875
response = self._client.call('Branch.set_last_revision',
876
path, self._lock_token, self._repo_lock_token, rev_id)
877
if response[0] == 'NoSuchRevision':
878
raise NoSuchRevision(self, rev_id)
880
assert response == ('ok',), (
881
'unexpected response code %r' % (response,))
882
self._cache_revision_history(rev_history)
884
def get_parent(self):
886
return self._real_branch.get_parent()
888
def set_parent(self, url):
890
return self._real_branch.set_parent(url)
892
def get_config(self):
893
return RemoteBranchConfig(self)
895
def sprout(self, to_bzrdir, revision_id=None):
896
# Like Branch.sprout, except that it sprouts a branch in the default
897
# format, because RemoteBranches can't be created at arbitrary URLs.
898
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
899
# to_bzrdir.create_branch...
901
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
902
self._real_branch.copy_content_into(result, revision_id=revision_id)
903
result.set_parent(self.bzrdir.root_transport.base)
907
def append_revision(self, *revision_ids):
909
return self._real_branch.append_revision(*revision_ids)
912
def pull(self, source, overwrite=False, stop_revision=None):
914
self._real_branch.pull(
915
source, overwrite=overwrite, stop_revision=stop_revision)
918
def push(self, target, overwrite=False, stop_revision=None):
920
return self._real_branch.push(
921
target, overwrite=overwrite, stop_revision=stop_revision)
924
return self._lock_count >= 1
926
def set_last_revision_info(self, revno, revision_id):
928
self._clear_cached_state()
929
return self._real_branch.set_last_revision_info(revno, revision_id)
931
def generate_revision_history(self, revision_id, last_rev=None,
934
return self._real_branch.generate_revision_history(
935
revision_id, last_rev=last_rev, other_branch=other_branch)
940
return self._real_branch.tags
942
def set_push_location(self, location):
944
return self._real_branch.set_push_location(location)
946
def update_revisions(self, other, stop_revision=None):
948
return self._real_branch.update_revisions(
949
other, stop_revision=stop_revision)
952
class RemoteBranchConfig(BranchConfig):
955
self.branch._ensure_real()
956
return self.branch._real_branch.get_config().username()
958
def _get_branch_data_config(self):
959
self.branch._ensure_real()
960
if self._branch_data_config is None:
961
self._branch_data_config = TreeConfig(self.branch._real_branch)
962
return self._branch_data_config