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.call2(
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.call2('Repository.gather_stats', path,
305
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.call2('Branch.get_config_file', path)
635
assert response[0][0] == 'ok', \
636
'unexpected response code %s' % (response[0],)
637
return StringIO(response[1].read_body_bytes())
640
return LockableFiles.get(self, path)
643
class RemoteBranchFormat(branch.BranchFormat):
645
def __eq__(self, other):
646
return (isinstance(other, RemoteBranchFormat) and
647
self.__dict__ == other.__dict__)
649
def get_format_description(self):
650
return 'Remote BZR Branch'
652
def get_format_string(self):
653
return 'Remote BZR Branch'
655
def open(self, a_bzrdir):
656
assert isinstance(a_bzrdir, RemoteBzrDir)
657
return a_bzrdir.open_branch()
659
def initialize(self, a_bzrdir):
660
assert isinstance(a_bzrdir, RemoteBzrDir)
661
return a_bzrdir.create_branch()
664
class RemoteBranch(branch.Branch):
665
"""Branch stored on a server accessed by HPSS RPC.
667
At the moment most operations are mapped down to simple file operations.
670
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
672
"""Create a RemoteBranch instance.
674
:param real_branch: An optional local implementation of the branch
675
format, usually accessing the data via the VFS.
676
:param _client: Private parameter for testing.
678
#branch.Branch.__init__(self)
679
self._revision_history_cache = None
680
self.bzrdir = remote_bzrdir
681
if _client is not None:
682
self._client = _client
684
self._client = client.SmartClient(self.bzrdir._medium)
685
self.repository = remote_repository
686
if real_branch is not None:
687
self._real_branch = real_branch
688
# Give the remote repository the matching real repo.
689
real_repo = self._real_branch.repository
690
if isinstance(real_repo, RemoteRepository):
691
real_repo._ensure_real()
692
real_repo = real_repo._real_repository
693
self.repository._set_real_repository(real_repo)
694
# Give the branch the remote repository to let fast-pathing happen.
695
self._real_branch.repository = self.repository
697
self._real_branch = None
698
# Fill out expected attributes of branch for bzrlib api users.
699
self._format = RemoteBranchFormat()
700
self.base = self.bzrdir.root_transport.base
701
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
702
self._lock_mode = None
703
self._lock_token = None
705
self._leave_lock = False
707
def _ensure_real(self):
708
"""Ensure that there is a _real_branch set.
710
used before calls to self._real_branch.
712
if not self._real_branch:
713
assert vfs.vfs_enabled()
714
self.bzrdir._ensure_real()
715
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
716
# Give the remote repository the matching real repo.
717
real_repo = self._real_branch.repository
718
if isinstance(real_repo, RemoteRepository):
719
real_repo._ensure_real()
720
real_repo = real_repo._real_repository
721
self.repository._set_real_repository(real_repo)
722
# Give the branch the remote repository to let fast-pathing happen.
723
self._real_branch.repository = self.repository
724
# XXX: deal with _lock_mode == 'w'
725
if self._lock_mode == 'r':
726
self._real_branch.lock_read()
728
def get_physical_lock_status(self):
729
"""See Branch.get_physical_lock_status()."""
730
# should be an API call to the server, as branches must be lockable.
732
return self._real_branch.get_physical_lock_status()
735
if not self._lock_mode:
736
self._lock_mode = 'r'
738
if self._real_branch is not None:
739
self._real_branch.lock_read()
741
self._lock_count += 1
743
def _remote_lock_write(self, tokens):
745
branch_token = repo_token = ''
747
branch_token, repo_token = tokens
748
path = self.bzrdir._path_for_remote_call(self._client)
749
response = self._client.call('Branch.lock_write', path, branch_token,
751
if response[0] == 'ok':
752
ok, branch_token, repo_token = response
753
return branch_token, repo_token
754
elif response[0] == 'LockContention':
755
raise errors.LockContention('(remote lock)')
756
elif response[0] == 'TokenMismatch':
757
raise errors.TokenMismatch(tokens, '(remote tokens)')
758
elif response[0] == 'UnlockableTransport':
759
raise errors.UnlockableTransport(self.bzrdir.root_transport)
760
elif response[0] == 'ReadOnlyError':
761
raise errors.ReadOnlyError(self)
763
assert False, 'unexpected response code %r' % (response,)
765
def lock_write(self, tokens=None):
766
if not self._lock_mode:
767
remote_tokens = self._remote_lock_write(tokens)
768
self._lock_token, self._repo_lock_token = remote_tokens
769
assert self._lock_token, 'Remote server did not return a token!'
770
# TODO: We really, really, really don't want to call _ensure_real
771
# here, but it's the easiest way to ensure coherency between the
772
# state of the RemoteBranch and RemoteRepository objects and the
773
# physical locks. If we don't materialise the real objects here,
774
# then getting everything in the right state later is complex, so
775
# for now we just do it the lazy way.
776
# -- Andrew Bennetts, 2007-02-22.
778
if self._real_branch is not None:
779
self._real_branch.lock_write(tokens=remote_tokens)
780
if tokens is not None:
781
self._leave_lock = True
783
# XXX: this case seems to be unreachable; tokens cannot be None.
784
self._leave_lock = False
785
self._lock_mode = 'w'
787
elif self._lock_mode == 'r':
788
raise errors.ReadOnlyTransaction
790
if tokens is not None:
791
# Tokens were given to lock_write, and we're relocking, so check
792
# that the given tokens actually match the ones we already have.
793
held_tokens = (self._lock_token, self._repo_lock_token)
794
if tokens != held_tokens:
795
raise errors.TokenMismatch(str(tokens), str(held_tokens))
796
self._lock_count += 1
797
return self._lock_token, self._repo_lock_token
799
def _unlock(self, branch_token, repo_token):
800
path = self.bzrdir._path_for_remote_call(self._client)
801
response = self._client.call('Branch.unlock', path, branch_token,
803
if response == ('ok',):
805
elif response[0] == 'TokenMismatch':
806
raise errors.TokenMismatch(
807
str((branch_token, repo_token)), '(remote tokens)')
809
assert False, 'unexpected response code %s' % (response,)
812
self._lock_count -= 1
813
if not self._lock_count:
814
self._clear_cached_state()
815
mode = self._lock_mode
816
self._lock_mode = None
817
if self._real_branch is not None:
818
if not self._leave_lock:
819
# If this RemoteBranch will remove the physical lock for the
820
# repository, make sure the _real_branch doesn't do it
821
# first. (Because the _real_branch's repository is set to
822
# be the RemoteRepository.)
823
self._real_branch.repository.leave_lock_in_place()
824
self._real_branch.unlock()
827
assert self._lock_token, 'Locked, but no token!'
828
branch_token = self._lock_token
829
repo_token = self._repo_lock_token
830
self._lock_token = None
831
self._repo_lock_token = None
832
if not self._leave_lock:
833
self._unlock(branch_token, repo_token)
835
def break_lock(self):
837
return self._real_branch.break_lock()
839
def leave_lock_in_place(self):
840
self._leave_lock = True
842
def dont_leave_lock_in_place(self):
843
self._leave_lock = False
845
def last_revision_info(self):
846
"""See Branch.last_revision_info()."""
847
path = self.bzrdir._path_for_remote_call(self._client)
848
response = self._client.call('Branch.last_revision_info', path)
849
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
850
revno = int(response[1])
851
last_revision = response[2]
852
if last_revision == '':
853
last_revision = NULL_REVISION
854
return (revno, last_revision)
856
def _gen_revision_history(self):
857
"""See Branch._gen_revision_history()."""
858
path = self.bzrdir._path_for_remote_call(self._client)
859
response = self._client.call2('Branch.revision_history', path)
860
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
861
result = response[1].read_body_bytes().split('\x00')
867
def set_revision_history(self, rev_history):
868
# Send just the tip revision of the history; the server will generate
869
# the full history from that. If the revision doesn't exist in this
870
# branch, NoSuchRevision will be raised.
871
path = self.bzrdir._path_for_remote_call(self._client)
872
if rev_history == []:
875
rev_id = rev_history[-1]
876
response = self._client.call('Branch.set_last_revision',
877
path, self._lock_token, self._repo_lock_token, rev_id)
878
if response[0] == 'NoSuchRevision':
879
raise NoSuchRevision(self, rev_id)
881
assert response == ('ok',), (
882
'unexpected response code %r' % (response,))
883
self._cache_revision_history(rev_history)
885
def get_parent(self):
887
return self._real_branch.get_parent()
889
def set_parent(self, url):
891
return self._real_branch.set_parent(url)
893
def get_config(self):
894
return RemoteBranchConfig(self)
896
def sprout(self, to_bzrdir, revision_id=None):
897
# Like Branch.sprout, except that it sprouts a branch in the default
898
# format, because RemoteBranches can't be created at arbitrary URLs.
899
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
900
# to_bzrdir.create_branch...
902
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
903
self._real_branch.copy_content_into(result, revision_id=revision_id)
904
result.set_parent(self.bzrdir.root_transport.base)
908
def append_revision(self, *revision_ids):
910
return self._real_branch.append_revision(*revision_ids)
913
def pull(self, source, overwrite=False, stop_revision=None):
915
self._real_branch.pull(
916
source, overwrite=overwrite, stop_revision=stop_revision)
919
def push(self, target, overwrite=False, stop_revision=None):
921
return self._real_branch.push(
922
target, overwrite=overwrite, stop_revision=stop_revision)
925
return self._lock_count >= 1
927
def set_last_revision_info(self, revno, revision_id):
929
self._clear_cached_state()
930
return self._real_branch.set_last_revision_info(revno, revision_id)
932
def generate_revision_history(self, revision_id, last_rev=None,
935
return self._real_branch.generate_revision_history(
936
revision_id, last_rev=last_rev, other_branch=other_branch)
941
return self._real_branch.tags
943
def set_push_location(self, location):
945
return self._real_branch.set_push_location(location)
947
def update_revisions(self, other, stop_revision=None):
949
return self._real_branch.update_revisions(
950
other, stop_revision=stop_revision)
953
class RemoteBranchConfig(BranchConfig):
956
self.branch._ensure_real()
957
return self.branch._real_branch.get_config().username()
959
def _get_branch_data_config(self):
960
self.branch._ensure_real()
961
if self._branch_data_config is None:
962
self._branch_data_config = TreeConfig(self.branch._real_branch)
963
return self._branch_data_config